core cleanup: introduce core init/destroy functions
[siplcs.git] / src / core / sipe.c
blobfee7ead367a4df711cd9d2002663b1f254744812
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #ifdef _WIN32
41 #include "win32dep.h" /* for LOCALEDIR */
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 /* for network */
48 #include "libc_interface.h"
49 #else
50 #include <sys/types.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <errno.h>
59 #include <string.h>
60 #include <unistd.h>
62 #include <glib.h>
64 #include "sipe-common.h"
66 #include "account.h"
67 #include "blist.h"
68 #include "connection.h"
69 #include "conversation.h"
70 #include "core.h"
71 #include "cipher.h"
72 #include "circbuffer.h"
73 #include "debug.h"
74 #include "dnsquery.h"
75 #include "dnssrv.h"
76 #include "ft.h"
77 #include "mime.h"
78 #include "network.h"
79 #include "notify.h"
80 #include "plugin.h"
81 #include "privacy.h"
82 #include "request.h"
83 #include "savedstatuses.h"
84 #include "sslconn.h"
85 #include "util.h"
86 #include "version.h"
87 #include "xmlnode.h"
89 #include "core-depurple.h" /* Temporary for the core de-purple transition */
91 #include "sipmsg.h"
92 #include "sip-csta.h"
93 #include "sip-sec.h"
94 #include "sipe-backend-debug.h"
95 #include "sipe-cal.h"
96 #include "sipe-chat.h"
97 #include "sipe-conf.h"
98 #include "sipe-core-api.h"
99 #include "sipe-dialog.h"
100 #include "sipe-ews.h"
101 #include "sipe-ft.h"
102 #include "sipe-nls.h"
103 #include "sipe-session.h"
104 #include "sipe-sign.h"
105 #include "sipe-utils.h"
106 #include "sipe-xml.h"
107 #include "http-conn.h"
108 #include "uuid.h"
109 #include "sipe.h"
111 /* Backward compatibility when compiling against 2.4.x API */
112 #if !PURPLE_VERSION_CHECK(2,5,0)
113 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
114 #endif
116 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
118 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
119 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
121 /* Keep in sync with sipe_transport_type! */
122 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
123 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
125 /* Status identifiers (see also: sipe_status_types()) */
126 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
127 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
128 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
129 /* PURPLE_STATUS_UNAVAILABLE: */
130 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
131 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
132 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
133 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
134 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
135 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
136 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
137 /* PURPLE_STATUS_AWAY: */
138 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
139 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
140 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
141 /** Reuters status (user settable) */
142 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
143 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
144 /* ??? PURPLE_STATUS_MOBILE */
145 /* ??? PURPLE_STATUS_TUNE */
147 /* Status attributes (see also sipe_status_types() */
148 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
150 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
152 static struct sipe_activity_map_struct
154 sipe_activity type;
155 const char *token;
156 const char *desc;
157 const char *status_id;
159 } const sipe_activity_map[] =
161 /* This has nothing to do with Availability numbers, like 3500 (online).
162 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
164 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
165 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
166 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
167 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
168 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
169 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
170 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
171 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
172 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
173 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
174 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
175 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
176 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
177 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
178 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
180 /** @param x is sipe_activity */
181 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
184 /* Action name templates */
185 #define ACTION_NAME_PRESENCE "<presence><%s>"
187 static sipe_activity
188 sipe_get_activity_by_token(const char *token)
190 int i;
192 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
194 if (sipe_strequal(token, sipe_activity_map[i].token))
195 return sipe_activity_map[i].type;
198 return sipe_activity_map[0].type;
201 static const char *
202 sipe_get_activity_desc_by_token(const char *token)
204 if (!token) return NULL;
206 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
209 /** Allows to send typed messages from chat window again after account reinstantiation. */
210 static void
211 sipe_rejoin_chat(PurpleConversation *conv)
213 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
214 PURPLE_CONV_CHAT(conv)->left)
216 PURPLE_CONV_CHAT(conv)->left = FALSE;
217 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
221 static char *genbranch()
223 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
224 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
225 rand() & 0xFFFF, rand() & 0xFFFF);
229 static char *default_ua = NULL;
230 static const char*
231 sipe_get_useragent(struct sipe_account_data *sip)
233 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
234 if (is_empty(useragent)) {
235 if (!default_ua) {
236 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
237 /* ref: lzodefs.h */
238 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
239 #define SIPE_TARGET_PLATFORM "linux"
240 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
241 #define SIPE_TARGET_PLATFORM "bsd"
242 #elif defined(__APPLE__) || defined(__MACOS__)
243 #define SIPE_TARGET_PLATFORM "macosx"
244 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
245 #define SIPE_TARGET_PLATFORM "aix"
246 #elif defined(__solaris__) || defined(__sun)
247 #define SIPE_TARGET_PLATFORM "sun"
248 #elif defined(_WIN32)
249 #define SIPE_TARGET_PLATFORM "win"
250 #elif defined(__CYGWIN__)
251 #define SIPE_TARGET_PLATFORM "cygwin"
252 #elif defined(__hpux__)
253 #define SIPE_TARGET_PLATFORM "hpux"
254 #elif defined(__sgi__)
255 #define SIPE_TARGET_PLATFORM "irix"
256 #else
257 #define SIPE_TARGET_PLATFORM "unknown"
258 #endif
260 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
261 #define SIPE_TARGET_ARCH "x86_64"
262 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
263 #define SIPE_TARGET_ARCH "i386"
264 #elif defined(__ppc64__)
265 #define SIPE_TARGET_ARCH "ppc64"
266 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
267 #define SIPE_TARGET_ARCH "ppc"
268 #elif defined(__hppa__) || defined(__hppa)
269 #define SIPE_TARGET_ARCH "hppa"
270 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
271 #define SIPE_TARGET_ARCH "mips"
272 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
273 #define SIPE_TARGET_ARCH "s390"
274 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
275 #define SIPE_TARGET_ARCH "sparc"
276 #elif defined(__arm__)
277 #define SIPE_TARGET_ARCH "arm"
278 #else
279 #define SIPE_TARGET_ARCH "other"
280 #endif
282 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
283 purple_core_get_version(),
284 sip->server_version ? sip->server_version : "");
286 useragent = default_ua;
288 return useragent;
291 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
292 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
294 return "sipe";
297 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
299 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
300 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
301 gpointer data);
303 static void sipe_close(PurpleConnection *gc);
305 static void send_presence_status(struct sipe_account_data *sip);
307 static void sendout_pkt(PurpleConnection *gc, const char *buf);
309 static void sipe_keep_alive(PurpleConnection *gc)
311 struct sipe_account_data *sip = gc->proto_data;
312 if (sip->transport == SIPE_TRANSPORT_UDP) {
313 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
314 gchar buf[2] = {0, 0};
315 purple_debug_info("sipe", "sending keep alive\n");
316 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
317 } else {
318 time_t now = time(NULL);
319 if ((sip->keepalive_timeout > 0) &&
320 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
321 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
323 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
324 sendout_pkt(gc, "\r\n\r\n");
325 sip->last_keepalive = now;
330 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
332 struct sip_connection *ret = NULL;
333 GSList *entry = sip->openconns;
334 while (entry) {
335 ret = entry->data;
336 if (ret->fd == fd) return ret;
337 entry = entry->next;
339 return NULL;
342 static void sipe_auth_free(struct sip_auth *auth)
344 g_free(auth->opaque);
345 auth->opaque = NULL;
346 g_free(auth->realm);
347 auth->realm = NULL;
348 g_free(auth->target);
349 auth->target = NULL;
350 auth->version = 0;
351 auth->type = AUTH_TYPE_UNSET;
352 auth->retries = 0;
353 auth->expires = 0;
354 g_free(auth->gssapi_data);
355 auth->gssapi_data = NULL;
356 sip_sec_destroy_context(auth->gssapi_context);
357 auth->gssapi_context = NULL;
360 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
362 struct sip_connection *ret = g_new0(struct sip_connection, 1);
363 ret->fd = fd;
364 sip->openconns = g_slist_append(sip->openconns, ret);
365 return ret;
368 static void connection_remove(struct sipe_account_data *sip, int fd)
370 struct sip_connection *conn = connection_find(sip, fd);
371 if (conn) {
372 sip->openconns = g_slist_remove(sip->openconns, conn);
373 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
374 g_free(conn->inbuf);
375 g_free(conn);
379 static void connection_free_all(struct sipe_account_data *sip)
381 struct sip_connection *ret = NULL;
382 GSList *entry = sip->openconns;
383 while (entry) {
384 ret = entry->data;
385 connection_remove(sip, ret->fd);
386 entry = sip->openconns;
390 static void
391 sipe_make_signature(struct sipe_account_data *sip,
392 struct sipmsg *msg);
394 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
396 gchar noncecount[9];
397 const char *authuser = sip->authuser;
398 gchar *response;
399 gchar *ret;
401 if (!authuser || strlen(authuser) < 1) {
402 authuser = sip->username;
405 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
406 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
407 gchar *version_str;
409 // If we have a signature for the message, include that
410 if (msg->signature) {
411 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);
414 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
415 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
416 gchar *gssapi_data;
417 gchar *opaque;
418 gchar *sign_str = NULL;
420 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
421 &(auth->expires),
422 auth->type,
423 purple_account_get_bool(sip->account, "sso", TRUE),
424 sip->authdomain ? sip->authdomain : "",
425 authuser,
426 sip->password,
427 auth->target,
428 auth->gssapi_data);
429 if (!gssapi_data || !auth->gssapi_context) {
430 sip->gc->wants_to_die = TRUE;
431 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
432 return NULL;
435 if (auth->version > 3) {
436 sipe_make_signature(sip, msg);
437 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
438 msg->rand, msg->num, msg->signature);
439 } else {
440 sign_str = g_strdup("");
443 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
444 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
445 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"%s%s", auth_protocol, opaque, auth->realm, auth->target, gssapi_data, version_str, sign_str);
446 g_free(opaque);
447 g_free(gssapi_data);
448 g_free(version_str);
449 g_free(sign_str);
450 return ret;
453 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
454 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
455 g_free(version_str);
456 return ret;
458 } else { /* Digest */
460 /* Calculate new session key */
461 if (!auth->opaque) {
462 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
463 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
464 authuser, auth->realm, sip->password,
465 auth->gssapi_data, NULL);
468 sprintf(noncecount, "%08d", auth->nc++);
469 response = purple_cipher_http_digest_calculate_response("md5",
470 msg->method, msg->target, NULL, NULL,
471 auth->gssapi_data, noncecount, NULL,
472 auth->opaque);
473 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
475 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);
476 g_free(response);
477 return ret;
481 static char *parse_attribute(const char *attrname, const char *source)
483 const char *tmp, *tmp2;
484 char *retval = NULL;
485 int len = strlen(attrname);
487 if (g_str_has_prefix(source, attrname)) {
488 tmp = source + len;
489 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
490 if (tmp2)
491 retval = g_strndup(tmp, tmp2 - tmp);
492 else
493 retval = g_strdup(tmp);
496 return retval;
499 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
501 int i;
502 gchar **parts;
504 if (!hdr) {
505 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
506 return;
509 if (!g_strncasecmp(hdr, "NTLM", 4)) {
510 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
511 auth->type = AUTH_TYPE_NTLM;
512 hdr += 5;
513 auth->nc = 1;
514 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
515 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
516 auth->type = AUTH_TYPE_KERBEROS;
517 hdr += 9;
518 auth->nc = 3;
519 } else {
520 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
521 auth->type = AUTH_TYPE_DIGEST;
522 hdr += 7;
525 parts = g_strsplit(hdr, "\", ", 0);
526 for (i = 0; parts[i]; i++) {
527 char *tmp;
529 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
531 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
532 g_free(auth->gssapi_data);
533 auth->gssapi_data = tmp;
535 if (auth->type == AUTH_TYPE_NTLM) {
536 /* NTLM module extracts nonce from gssapi-data */
537 auth->nc = 3;
540 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
541 /* Only used with AUTH_TYPE_DIGEST */
542 g_free(auth->gssapi_data);
543 auth->gssapi_data = tmp;
544 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
545 g_free(auth->opaque);
546 auth->opaque = tmp;
547 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
548 g_free(auth->realm);
549 auth->realm = tmp;
551 if (auth->type == AUTH_TYPE_DIGEST) {
552 /* Throw away old session key */
553 g_free(auth->opaque);
554 auth->opaque = NULL;
555 auth->nc = 1;
557 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
558 g_free(auth->target);
559 auth->target = tmp;
560 } else if ((tmp = parse_attribute("version=", parts[i]))) {
561 auth->version = atoi(tmp);
562 g_free(tmp);
564 // uncomment to revert to previous functionality if version 3+ does not work.
565 // auth->version = 2;
567 g_strfreev(parts);
569 return;
572 static void sipe_canwrite_cb(gpointer data,
573 SIPE_UNUSED_PARAMETER gint source,
574 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
576 PurpleConnection *gc = data;
577 struct sipe_account_data *sip = gc->proto_data;
578 gsize max_write;
579 gssize written;
581 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
583 if (max_write == 0) {
584 if (sip->tx_handler != 0){
585 purple_input_remove(sip->tx_handler);
586 sip->tx_handler = 0;
588 return;
591 written = write(sip->fd, sip->txbuf->outptr, max_write);
593 if (written < 0 && errno == EAGAIN)
594 written = 0;
595 else if (written <= 0) {
596 /*TODO: do we really want to disconnect on a failure to write?*/
597 purple_connection_error(gc, _("Could not write"));
598 return;
601 purple_circ_buffer_mark_read(sip->txbuf, written);
604 static void sipe_canwrite_cb_ssl(gpointer data,
605 SIPE_UNUSED_PARAMETER gint src,
606 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
608 PurpleConnection *gc = data;
609 struct sipe_account_data *sip = gc->proto_data;
610 gsize max_write;
611 gssize written;
613 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
615 if (max_write == 0) {
616 if (sip->tx_handler != 0) {
617 purple_input_remove(sip->tx_handler);
618 sip->tx_handler = 0;
619 return;
623 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
625 if (written < 0 && errno == EAGAIN)
626 written = 0;
627 else if (written <= 0) {
628 /*TODO: do we really want to disconnect on a failure to write?*/
629 purple_connection_error(gc, _("Could not write"));
630 return;
633 purple_circ_buffer_mark_read(sip->txbuf, written);
636 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
638 static void send_later_cb(gpointer data, gint source,
639 SIPE_UNUSED_PARAMETER const gchar *error)
641 PurpleConnection *gc = data;
642 struct sipe_account_data *sip;
643 struct sip_connection *conn;
645 if (!PURPLE_CONNECTION_IS_VALID(gc))
647 if (source >= 0)
648 close(source);
649 return;
652 if (source < 0) {
653 purple_connection_error(gc, _("Could not connect"));
654 return;
657 sip = gc->proto_data;
658 sip->fd = source;
659 sip->connecting = FALSE;
660 sip->last_keepalive = time(NULL);
662 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
664 /* If there is more to write now, we need to register a handler */
665 if (sip->txbuf->bufused > 0)
666 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
668 conn = connection_create(sip, source);
669 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
672 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
674 struct sipe_account_data *sip;
676 if (!PURPLE_CONNECTION_IS_VALID(gc))
678 if (gsc) purple_ssl_close(gsc);
679 return NULL;
682 sip = gc->proto_data;
683 sip->fd = gsc->fd;
684 sip->gsc = gsc;
685 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
686 sip->connecting = FALSE;
687 sip->last_keepalive = time(NULL);
689 connection_create(sip, gsc->fd);
691 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
693 return sip;
696 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
697 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
699 PurpleConnection *gc = data;
700 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
701 if (sip == NULL) return;
703 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
705 /* If there is more to write now */
706 if (sip->txbuf->bufused > 0) {
707 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
712 static void sendlater(PurpleConnection *gc, const char *buf)
714 struct sipe_account_data *sip = gc->proto_data;
716 if (!sip->connecting) {
717 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
718 if (sip->transport == SIPE_TRANSPORT_TLS){
719 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
720 } else {
721 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
722 purple_connection_error(gc, _("Could not create socket"));
725 sip->connecting = TRUE;
728 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
729 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
731 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
734 static void sendout_pkt(PurpleConnection *gc, const char *buf)
736 struct sipe_account_data *sip = gc->proto_data;
737 time_t currtime = time(NULL);
738 int writelen = strlen(buf);
739 char *tmp;
741 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
742 g_free(tmp);
743 if (sip->transport == SIPE_TRANSPORT_UDP) {
744 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
745 purple_debug_info("sipe", "could not send packet\n");
747 } else {
748 int ret;
749 if (sip->fd < 0) {
750 sendlater(gc, buf);
751 return;
754 if (sip->tx_handler) {
755 ret = -1;
756 errno = EAGAIN;
757 } else{
758 if (sip->gsc){
759 ret = purple_ssl_write(sip->gsc, buf, writelen);
760 }else{
761 ret = write(sip->fd, buf, writelen);
765 if (ret < 0 && errno == EAGAIN)
766 ret = 0;
767 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
768 sendlater(gc, buf);
769 return;
772 if (ret < writelen) {
773 if (!sip->tx_handler){
774 if (sip->gsc){
775 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
777 else{
778 sip->tx_handler = purple_input_add(sip->fd,
779 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
780 gc);
784 /* XXX: is it OK to do this? You might get part of a request sent
785 with part of another. */
786 if (sip->txbuf->bufused > 0)
787 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
789 purple_circ_buffer_append(sip->txbuf, buf + ret,
790 writelen - ret);
795 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
797 sendout_pkt(gc, buf);
798 return len;
801 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
803 GSList *tmp = msg->headers;
804 gchar *name;
805 gchar *value;
806 GString *outstr = g_string_new("");
807 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
808 while (tmp) {
809 name = ((struct sipnameval*) (tmp->data))->name;
810 value = ((struct sipnameval*) (tmp->data))->value;
811 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
812 tmp = g_slist_next(tmp);
814 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
815 sendout_pkt(sip->gc, outstr->str);
816 g_string_free(outstr, TRUE);
819 static void
820 sipe_make_signature(struct sipe_account_data *sip,
821 struct sipmsg *msg)
823 if (sip->registrar.gssapi_context) {
824 struct sipmsg_breakdown msgbd;
825 gchar *signature_input_str;
826 msgbd.msg = msg;
827 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
828 msgbd.rand = g_strdup_printf("%08x", g_random_int());
829 sip->registrar.ntlm_num++;
830 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
831 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
832 if (signature_input_str != NULL) {
833 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
834 msg->signature = signature_hex;
835 msg->rand = g_strdup(msgbd.rand);
836 msg->num = g_strdup(msgbd.num);
837 g_free(signature_input_str);
839 sipmsg_breakdown_free(&msgbd);
843 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
845 gchar * buf;
847 if (sip->registrar.type == AUTH_TYPE_UNSET) {
848 return;
851 sipe_make_signature(sip, msg);
853 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
854 buf = auth_header(sip, &sip->registrar, msg);
855 if (buf) {
856 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
858 g_free(buf);
859 } else if (sipe_strequal(method,"SUBSCRIBE") || sipe_strequal(method,"SERVICE") || sipe_strequal(method,"MESSAGE") || sipe_strequal(method,"INVITE") || sipe_strequal(method, "ACK") || sipe_strequal(method, "NOTIFY") || sipe_strequal(method, "BYE") || sipe_strequal(method, "INFO") || sipe_strequal(method, "OPTIONS") || sipe_strequal(method, "REFER")) {
860 sip->registrar.nc = 3;
861 sip->registrar.type = AUTH_TYPE_NTLM;
862 #ifdef HAVE_KERBEROS
863 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
864 sip->registrar.type = AUTH_TYPE_KERBEROS;
866 #endif
869 buf = auth_header(sip, &sip->registrar, msg);
870 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
871 g_free(buf);
872 } else {
873 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
877 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
878 const char *text, const char *body)
880 gchar *name;
881 gchar *value;
882 GString *outstr = g_string_new("");
883 struct sipe_account_data *sip = gc->proto_data;
884 gchar *contact;
885 GSList *tmp;
886 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
888 /* Can return NULL! */
889 contact = get_contact(sip);
890 if (contact) {
891 sipmsg_add_header(msg, "Contact", contact);
892 g_free(contact);
895 if (body) {
896 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
897 sipmsg_add_header(msg, "Content-Length", len);
898 g_free(len);
899 } else {
900 sipmsg_add_header(msg, "Content-Length", "0");
903 msg->response = code;
905 sipmsg_strip_headers(msg, keepers);
906 sipmsg_merge_new_headers(msg);
907 sign_outgoing_message(msg, sip, msg->method);
909 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
910 tmp = msg->headers;
911 while (tmp) {
912 name = ((struct sipnameval*) (tmp->data))->name;
913 value = ((struct sipnameval*) (tmp->data))->value;
915 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
916 tmp = g_slist_next(tmp);
918 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
919 sendout_pkt(gc, outstr->str);
920 g_string_free(outstr, TRUE);
923 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
925 if (sip->transactions) {
926 sip->transactions = g_slist_remove(sip->transactions, trans);
927 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
929 if (trans->msg) sipmsg_free(trans->msg);
930 if (trans->payload) {
931 (*trans->payload->destroy)(trans->payload->data);
932 g_free(trans->payload);
934 g_free(trans->key);
935 g_free(trans);
939 static struct transaction *
940 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
942 const gchar *call_id;
943 const gchar *cseq;
944 struct transaction *trans = g_new0(struct transaction, 1);
946 trans->time = time(NULL);
947 trans->msg = (struct sipmsg *)msg;
948 call_id = sipmsg_find_header(trans->msg, "Call-ID");
949 cseq = sipmsg_find_header(trans->msg, "CSeq");
950 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
951 trans->callback = callback;
952 sip->transactions = g_slist_append(sip->transactions, trans);
953 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
954 return trans;
957 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
959 struct transaction *trans;
960 GSList *transactions = sip->transactions;
961 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
962 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
963 gchar *key;
965 if (!call_id || !cseq) {
966 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
967 return NULL;
970 key = g_strdup_printf("<%s><%s>", call_id, cseq);
971 while (transactions) {
972 trans = transactions->data;
973 if (!g_strcasecmp(trans->key, key)) {
974 g_free(key);
975 return trans;
977 transactions = transactions->next;
980 g_free(key);
981 return NULL;
984 struct transaction *
985 send_sip_request(PurpleConnection *gc, const gchar *method,
986 const gchar *url, const gchar *to, const gchar *addheaders,
987 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
989 struct sipe_account_data *sip = gc->proto_data;
990 const char *addh = "";
991 char *buf;
992 struct sipmsg *msg;
993 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
994 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
995 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
996 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
997 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
998 gchar *route = g_strdup("");
999 gchar *epid = get_epid(sip);
1000 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
1001 struct transaction *trans = NULL;
1003 if (dialog && dialog->routes)
1005 GSList *iter = dialog->routes;
1007 while(iter)
1009 char *tmp = route;
1010 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1011 g_free(tmp);
1012 iter = g_slist_next(iter);
1016 if (!ourtag && !dialog) {
1017 ourtag = gentag();
1020 if (sipe_strequal(method, "REGISTER")) {
1021 if (sip->regcallid) {
1022 g_free(callid);
1023 callid = g_strdup(sip->regcallid);
1024 } else {
1025 sip->regcallid = g_strdup(callid);
1027 cseq = ++sip->cseq;
1030 if (addheaders) addh = addheaders;
1032 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1033 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1034 "From: <sip:%s>%s%s;epid=%s\r\n"
1035 "To: <%s>%s%s%s%s\r\n"
1036 "Max-Forwards: 70\r\n"
1037 "CSeq: %d %s\r\n"
1038 "User-Agent: %s\r\n"
1039 "Call-ID: %s\r\n"
1040 "%s%s"
1041 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1042 method,
1043 dialog && dialog->request ? dialog->request : url,
1044 TRANSPORT_DESCRIPTOR,
1045 purple_network_get_my_ip(-1),
1046 sip->listenport,
1047 branch ? ";branch=" : "",
1048 branch ? branch : "",
1049 sip->username,
1050 ourtag ? ";tag=" : "",
1051 ourtag ? ourtag : "",
1052 epid,
1054 theirtag ? ";tag=" : "",
1055 theirtag ? theirtag : "",
1056 theirepid ? ";epid=" : "",
1057 theirepid ? theirepid : "",
1058 cseq,
1059 method,
1060 sipe_get_useragent(sip),
1061 callid,
1062 route,
1063 addh,
1064 body ? (gsize) strlen(body) : 0,
1065 body ? body : "");
1068 //printf ("parsing msg buf:\n%s\n\n", buf);
1069 msg = sipmsg_parse_msg(buf);
1071 g_free(buf);
1072 g_free(ourtag);
1073 g_free(theirtag);
1074 g_free(theirepid);
1075 g_free(branch);
1076 g_free(callid);
1077 g_free(route);
1078 g_free(epid);
1080 sign_outgoing_message (msg, sip, method);
1082 buf = sipmsg_to_string (msg);
1084 /* add to ongoing transactions */
1085 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1086 if (!sipe_strequal(method, "ACK")) {
1087 trans = transactions_add_buf(sip, msg, tc);
1088 } else {
1089 sipmsg_free(msg);
1091 sendout_pkt(gc, buf);
1092 g_free(buf);
1094 return trans;
1098 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1100 static void
1101 send_soap_request_with_cb(struct sipe_account_data *sip,
1102 gchar *from0,
1103 gchar *body,
1104 TransCallback callback,
1105 struct transaction_payload *payload)
1107 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1108 gchar *contact = get_contact(sip);
1109 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1110 "Content-Type: application/SOAP+xml\r\n",contact);
1112 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1113 trans->payload = payload;
1115 g_free(from);
1116 g_free(contact);
1117 g_free(hdr);
1120 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1122 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1125 static char *get_contact_register(struct sipe_account_data *sip)
1127 char *epid = get_epid(sip);
1128 char *uuid = generateUUIDfromEPID(epid);
1129 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);
1130 g_free(uuid);
1131 g_free(epid);
1132 return(buf);
1135 static void do_register_exp(struct sipe_account_data *sip, int expire)
1137 char *uri;
1138 char *expires;
1139 char *to;
1140 char *contact;
1141 char *hdr;
1143 if (!sip->sipdomain) return;
1145 uri = sip_uri_from_name(sip->sipdomain);
1146 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1147 to = sip_uri_self(sip);
1148 contact = get_contact_register(sip);
1149 hdr = g_strdup_printf("Contact: %s\r\n"
1150 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1151 "Event: registration\r\n"
1152 "Allow-Events: presence\r\n"
1153 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1154 "%s", contact, expires);
1155 g_free(contact);
1156 g_free(expires);
1158 sip->registerstatus = 1;
1160 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1161 process_register_response);
1163 g_free(hdr);
1164 g_free(uri);
1165 g_free(to);
1168 static void do_register_cb(struct sipe_account_data *sip,
1169 SIPE_UNUSED_PARAMETER void *unused)
1171 do_register_exp(sip, -1);
1172 sip->reregister_set = FALSE;
1175 static void do_register(struct sipe_account_data *sip)
1177 do_register_exp(sip, -1);
1180 static void
1181 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1183 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1184 send_soap_request(sip, body);
1185 g_free(body);
1188 static void
1189 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1191 if (allow) {
1192 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1193 } else {
1194 purple_debug_info("sipe", "Blocking contact %s\n", who);
1197 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1200 static
1201 void sipe_auth_user_cb(void * data)
1203 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1204 if (!job) return;
1206 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1207 g_free(job);
1210 static
1211 void sipe_deny_user_cb(void * data)
1213 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1214 if (!job) return;
1216 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1217 g_free(job);
1220 static void
1221 sipe_add_permit(PurpleConnection *gc, const char *name)
1223 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1224 sipe_contact_allow_deny(sip, name, TRUE);
1227 static void
1228 sipe_add_deny(PurpleConnection *gc, const char *name)
1230 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1231 sipe_contact_allow_deny(sip, name, FALSE);
1234 /*static void
1235 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1237 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1238 sipe_contact_set_acl(sip, name, "");
1241 static void
1242 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1244 xmlnode *watchers;
1245 xmlnode *watcher;
1246 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1247 if (msg->response != 0 && msg->response != 200) return;
1249 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1251 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1252 if (!watchers) return;
1254 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1255 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1256 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1257 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1259 // TODO pull out optional displayName to pass as alias
1260 if (remote_user) {
1261 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1262 job->who = remote_user;
1263 job->sip = sip;
1264 purple_account_request_authorization(
1265 sip->account,
1266 remote_user,
1267 _("you"), /* id */
1268 alias,
1269 NULL, /* message */
1270 on_list,
1271 sipe_auth_user_cb,
1272 sipe_deny_user_cb,
1273 (void *) job);
1278 xmlnode_free(watchers);
1279 return;
1282 static void
1283 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1285 PurpleGroup * purple_group = purple_find_group(group->name);
1286 if (!purple_group) {
1287 purple_group = purple_group_new(group->name);
1288 purple_blist_add_group(purple_group, NULL);
1291 if (purple_group) {
1292 group->purple_group = purple_group;
1293 sip->groups = g_slist_append(sip->groups, group);
1294 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1295 } else {
1296 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1300 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1302 struct sipe_group *group;
1303 GSList *entry;
1304 if (sip == NULL) {
1305 return NULL;
1308 entry = sip->groups;
1309 while (entry) {
1310 group = entry->data;
1311 if (group->id == id) {
1312 return group;
1314 entry = entry->next;
1316 return NULL;
1319 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1321 struct sipe_group *group;
1322 GSList *entry;
1323 if (!sip || !name) {
1324 return NULL;
1327 entry = sip->groups;
1328 while (entry) {
1329 group = entry->data;
1330 if (sipe_strequal(group->name, name)) {
1331 return group;
1333 entry = entry->next;
1335 return NULL;
1338 static void
1339 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1341 gchar *body;
1342 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1343 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1344 send_soap_request(sip, body);
1345 g_free(body);
1346 g_free(group->name);
1347 group->name = g_strdup(name);
1351 * Only appends if no such value already stored.
1352 * Like Set in Java.
1354 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1355 GSList * res = list;
1356 if (!g_slist_find_custom(list, data, func)) {
1357 res = g_slist_insert_sorted(list, data, func);
1359 return res;
1362 static int
1363 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1364 return group1->id - group2->id;
1368 * Returns string like "2 4 7 8" - group ids buddy belong to.
1370 static gchar *
1371 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1372 int i = 0;
1373 gchar *res;
1374 //creating array from GList, converting int to gchar*
1375 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1376 GSList *entry = buddy->groups;
1378 if (!ids_arr) return NULL;
1380 while (entry) {
1381 struct sipe_group * group = entry->data;
1382 ids_arr[i] = g_strdup_printf("%d", group->id);
1383 entry = entry->next;
1384 i++;
1386 ids_arr[i] = NULL;
1387 res = g_strjoinv(" ", ids_arr);
1388 g_strfreev(ids_arr);
1389 return res;
1393 * Sends buddy update to server
1395 static void
1396 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1398 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1399 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1401 if (buddy && purple_buddy) {
1402 const char *alias = purple_buddy_get_alias(purple_buddy);
1403 gchar *groups = sipe_get_buddy_groups_string(buddy);
1404 if (groups) {
1405 gchar *body;
1406 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1408 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1409 alias, groups, "true", buddy->name, sip->contacts_delta++
1411 send_soap_request(sip, body);
1412 g_free(groups);
1413 g_free(body);
1418 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1420 if (msg->response == 200) {
1421 struct sipe_group *group;
1422 struct group_user_context *ctx = trans->payload->data;
1423 xmlnode *xml;
1424 xmlnode *node;
1425 char *group_id;
1426 struct sipe_buddy *buddy;
1428 xml = xmlnode_from_str(msg->body, msg->bodylen);
1429 if (!xml) {
1430 return FALSE;
1433 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1434 if (!node) {
1435 xmlnode_free(xml);
1436 return FALSE;
1439 group_id = xmlnode_get_data(node);
1440 if (!group_id) {
1441 xmlnode_free(xml);
1442 return FALSE;
1445 group = g_new0(struct sipe_group, 1);
1446 group->id = (int)g_ascii_strtod(group_id, NULL);
1447 g_free(group_id);
1448 group->name = g_strdup(ctx->group_name);
1450 sipe_group_add(sip, group);
1452 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1453 if (buddy) {
1454 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1457 sipe_group_set_user(sip, ctx->user_name);
1459 xmlnode_free(xml);
1460 return TRUE;
1462 return FALSE;
1465 static void sipe_group_context_destroy(gpointer data)
1467 struct group_user_context *ctx = data;
1468 g_free(ctx->group_name);
1469 g_free(ctx->user_name);
1470 g_free(ctx);
1473 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1475 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1476 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1477 gchar *body;
1478 ctx->group_name = g_strdup(name);
1479 ctx->user_name = g_strdup(who);
1480 payload->destroy = sipe_group_context_destroy;
1481 payload->data = ctx;
1483 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1484 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1485 g_free(body);
1489 * Data structure for scheduled actions
1492 struct scheduled_action {
1494 * Name of action.
1495 * Format is <Event>[<Data>...]
1496 * Example: <presence><sip:user@domain.com> or <registration>
1498 gchar *name;
1499 guint timeout_handler;
1500 gboolean repetitive;
1501 Action action;
1502 GDestroyNotify destroy;
1503 struct sipe_account_data *sip;
1504 void *payload;
1508 * A timer callback
1509 * Should return FALSE if repetitive action is not needed
1511 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1513 gboolean ret;
1514 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1515 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1516 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1517 (sched_action->action)(sched_action->sip, sched_action->payload);
1518 ret = sched_action->repetitive;
1519 if (sched_action->destroy) {
1520 (*sched_action->destroy)(sched_action->payload);
1522 g_free(sched_action->name);
1523 g_free(sched_action);
1524 return ret;
1528 * Kills action timer effectively cancelling
1529 * scheduled action
1531 * @param name of action
1533 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1535 GSList *entry;
1537 if (!sip->timeouts || !name) return;
1539 entry = sip->timeouts;
1540 while (entry) {
1541 struct scheduled_action *sched_action = entry->data;
1542 if(sipe_strequal(sched_action->name, name)) {
1543 GSList *to_delete = entry;
1544 entry = entry->next;
1545 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1546 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1547 purple_timeout_remove(sched_action->timeout_handler);
1548 if (sched_action->destroy) {
1549 (*sched_action->destroy)(sched_action->payload);
1551 g_free(sched_action->name);
1552 g_free(sched_action);
1553 } else {
1554 entry = entry->next;
1559 static void
1560 sipe_schedule_action0(const gchar *name,
1561 int timeout,
1562 gboolean isSeconds,
1563 Action action,
1564 GDestroyNotify destroy,
1565 struct sipe_account_data *sip,
1566 void *payload)
1568 struct scheduled_action *sched_action;
1570 /* Make sure each action only exists once */
1571 sipe_cancel_scheduled_action(sip, name);
1573 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1574 sched_action = g_new0(struct scheduled_action, 1);
1575 sched_action->repetitive = FALSE;
1576 sched_action->name = g_strdup(name);
1577 sched_action->action = action;
1578 sched_action->destroy = destroy;
1579 sched_action->sip = sip;
1580 sched_action->payload = payload;
1581 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1582 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1583 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1584 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1587 void
1588 sipe_schedule_action(const gchar *name,
1589 int timeout,
1590 Action action,
1591 GDestroyNotify destroy,
1592 struct sipe_account_data *sip,
1593 void *payload)
1595 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1599 * Same as sipe_schedule_action() but timeout is in milliseconds.
1601 static void
1602 sipe_schedule_action_msec(const gchar *name,
1603 int timeout,
1604 Action action,
1605 GDestroyNotify destroy,
1606 struct sipe_account_data *sip,
1607 void *payload)
1609 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1612 static void
1613 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1614 time_t calculate_from);
1616 static int
1617 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1619 static const char*
1620 sipe_get_status_by_availability(int avail,
1621 char** activity);
1623 static void
1624 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1625 const char *status_id,
1626 const char *message,
1627 time_t do_not_publish[]);
1629 static void
1630 sipe_apply_calendar_status(struct sipe_account_data *sip,
1631 struct sipe_buddy *sbuddy,
1632 const char *status_id)
1634 time_t cal_avail_since;
1635 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1636 int avail;
1637 gchar *self_uri;
1639 if (!sbuddy) return;
1641 if (cal_status < SIPE_CAL_NO_DATA) {
1642 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1643 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1646 /* scheduled Cal update call */
1647 if (!status_id) {
1648 status_id = sbuddy->last_non_cal_status_id;
1649 g_free(sbuddy->activity);
1650 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1653 if (!status_id) {
1654 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1655 sbuddy->name ? sbuddy->name : "" );
1656 return;
1659 /* adjust to calendar status */
1660 if (cal_status != SIPE_CAL_NO_DATA) {
1661 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1663 if (cal_status == SIPE_CAL_BUSY
1664 && cal_avail_since > sbuddy->user_avail_since
1665 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1667 status_id = SIPE_STATUS_ID_BUSY;
1668 g_free(sbuddy->activity);
1669 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1671 avail = sipe_get_availability_by_status(status_id, NULL);
1673 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1674 if (cal_avail_since > sbuddy->activity_since) {
1675 if (cal_status == SIPE_CAL_OOF
1676 && avail >= 15000) /* 12000 in 2007 */
1678 g_free(sbuddy->activity);
1679 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1684 /* then set status_id actually */
1685 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1686 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1688 /* set our account state to the one in roaming (including calendar info) */
1689 self_uri = sip_uri_self(sip);
1690 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1691 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1692 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1695 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1696 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1698 g_free(self_uri);
1701 static void
1702 sipe_got_user_status(struct sipe_account_data *sip,
1703 const char* uri,
1704 const char *status_id)
1706 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1708 if (!sbuddy) return;
1710 /* Check if on 2005 system contact's calendar,
1711 * then set/preserve it.
1713 if (!sip->ocs2007) {
1714 sipe_apply_calendar_status(sip, sbuddy, status_id);
1715 } else {
1716 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1720 static void
1721 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1722 struct sipe_buddy *sbuddy,
1723 struct sipe_account_data *sip)
1725 sipe_apply_calendar_status(sip, sbuddy, NULL);
1729 * Updates contact's status
1730 * based on their calendar information.
1732 * Applicability: 2005 systems
1734 static void
1735 update_calendar_status(struct sipe_account_data *sip)
1737 purple_debug_info("sipe", "update_calendar_status() started.\n");
1738 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1740 /* repeat scheduling */
1741 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1745 * Schedules process of contacts' status update
1746 * based on their calendar information.
1747 * Should be scheduled to the beginning of every
1748 * 15 min interval, like:
1749 * 13:00, 13:15, 13:30, 13:45, etc.
1751 * Applicability: 2005 systems
1753 static void
1754 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1755 time_t calculate_from)
1757 int interval = 15*60;
1758 /** start of the beginning of closest 15 min interval. */
1759 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1761 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1762 asctime(localtime(&calculate_from)));
1763 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1764 asctime(localtime(&next_start)));
1766 sipe_schedule_action("<+2005-cal-status>",
1767 (int)(next_start - time(NULL)),
1768 (Action)update_calendar_status,
1769 NULL,
1770 sip,
1771 NULL);
1775 * Schedules process of self status publish
1776 * based on own calendar information.
1777 * Should be scheduled to the beginning of every
1778 * 15 min interval, like:
1779 * 13:00, 13:15, 13:30, 13:45, etc.
1781 * Applicability: 2007+ systems
1783 static void
1784 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1785 time_t calculate_from)
1787 int interval = 5*60;
1788 /** start of the beginning of closest 5 min interval. */
1789 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1791 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1792 asctime(localtime(&calculate_from)));
1793 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1794 asctime(localtime(&next_start)));
1796 sipe_schedule_action("<+2007-cal-status>",
1797 (int)(next_start - time(NULL)),
1798 (Action)publish_calendar_status_self,
1799 NULL,
1800 sip,
1801 NULL);
1804 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1806 /** Should be g_free()'d
1808 static gchar *
1809 sipe_get_subscription_key(const gchar *event,
1810 const gchar *with)
1812 gchar *key = NULL;
1814 if (is_empty(event)) return NULL;
1816 if (event && sipe_strcase_equal(event, "presence")) {
1817 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1818 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1820 /* @TODO drop participated buddies' just_added flag */
1821 } else if (event) {
1822 /* Subscription is identified by <event> key */
1823 key = g_strdup_printf("<%s>", event);
1826 return key;
1829 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1830 SIPE_UNUSED_PARAMETER struct transaction *trans)
1832 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1833 const gchar *event = sipmsg_find_header(msg, "Event");
1834 gchar *key;
1836 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1837 if (!event) {
1838 struct sipmsg *request_msg = trans->msg;
1839 event = sipmsg_find_header(request_msg, "Event");
1842 key = sipe_get_subscription_key(event, with);
1844 /* 200 OK; 481 Call Leg Does Not Exist */
1845 if (key && (msg->response == 200 || msg->response == 481)) {
1846 if (g_hash_table_lookup(sip->subscriptions, key)) {
1847 g_hash_table_remove(sip->subscriptions, key);
1848 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1852 /* create/store subscription dialog if not yet */
1853 if (msg->response == 200) {
1854 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1855 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1857 if (key) {
1858 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1859 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1861 subscription->dialog.callid = g_strdup(callid);
1862 subscription->dialog.cseq = atoi(cseq);
1863 subscription->dialog.with = g_strdup(with);
1864 subscription->event = g_strdup(event);
1865 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1867 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1870 g_free(cseq);
1873 g_free(key);
1874 g_free(with);
1876 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1878 process_incoming_notify(sip, msg, FALSE, FALSE);
1880 return TRUE;
1883 static void sipe_subscribe_resource_uri(const char *name,
1884 SIPE_UNUSED_PARAMETER gpointer value,
1885 gchar **resources_uri)
1887 gchar *tmp = *resources_uri;
1888 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1889 g_free(tmp);
1892 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1894 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1895 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1896 gchar *tmp = *resources_uri;
1898 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1900 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1901 g_free(tmp);
1905 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1906 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1907 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1908 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1909 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1912 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1914 gchar *key;
1915 gchar *contact = get_contact(sip);
1916 gchar *request;
1917 gchar *content;
1918 gchar *require = "";
1919 gchar *accept = "";
1920 gchar *autoextend = "";
1921 gchar *content_type;
1922 struct sip_dialog *dialog;
1924 if (sip->ocs2007) {
1925 require = ", categoryList";
1926 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1927 content_type = "application/msrtc-adrl-categorylist+xml";
1928 content = g_strdup_printf(
1929 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1930 "<action name=\"subscribe\" id=\"63792024\">\n"
1931 "<adhocList>\n%s</adhocList>\n"
1932 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1933 "<category name=\"calendarData\"/>\n"
1934 "<category name=\"contactCard\"/>\n"
1935 "<category name=\"note\"/>\n"
1936 "<category name=\"state\"/>\n"
1937 "</categoryList>\n"
1938 "</action>\n"
1939 "</batchSub>", sip->username, resources_uri);
1940 } else {
1941 autoextend = "Supported: com.microsoft.autoextend\r\n";
1942 content_type = "application/adrl+xml";
1943 content = g_strdup_printf(
1944 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1945 "<create xmlns=\"\">\n%s</create>\n"
1946 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1948 g_free(resources_uri);
1950 request = g_strdup_printf(
1951 "Require: adhoclist%s\r\n"
1952 "Supported: eventlist\r\n"
1953 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1954 "Supported: ms-piggyback-first-notify\r\n"
1955 "%sSupported: ms-benotify\r\n"
1956 "Proxy-Require: ms-benotify\r\n"
1957 "Event: presence\r\n"
1958 "Content-Type: %s\r\n"
1959 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1960 g_free(contact);
1962 /* subscribe to buddy presence */
1963 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1964 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1965 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1966 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1968 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1970 g_free(content);
1971 g_free(to);
1972 g_free(request);
1973 g_free(key);
1976 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1977 SIPE_UNUSED_PARAMETER void *unused)
1979 gchar *to = sip_uri_self(sip);
1980 gchar *resources_uri = g_strdup("");
1981 if (sip->ocs2007) {
1982 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1983 } else {
1984 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1987 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1990 struct presence_batched_routed {
1991 gchar *host;
1992 GSList *buddies;
1995 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1997 struct presence_batched_routed *data = payload;
1998 GSList *buddies = data->buddies;
1999 while (buddies) {
2000 g_free(buddies->data);
2001 buddies = buddies->next;
2003 g_slist_free(data->buddies);
2004 g_free(data->host);
2005 g_free(payload);
2008 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2010 struct presence_batched_routed *data = payload;
2011 GSList *buddies = data->buddies;
2012 gchar *resources_uri = g_strdup("");
2013 while (buddies) {
2014 gchar *tmp = resources_uri;
2015 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2016 g_free(tmp);
2017 buddies = buddies->next;
2019 sipe_subscribe_presence_batched_to(sip, resources_uri,
2020 g_strdup(data->host));
2024 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2025 * The user sends a single SUBSCRIBE request to the subscribed contact.
2026 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2030 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2033 gchar *key;
2034 gchar *to = sip_uri((char *)buddy_name);
2035 gchar *tmp = get_contact(sip);
2036 gchar *request;
2037 gchar *content = NULL;
2038 gchar *autoextend = "";
2039 gchar *content_type = "";
2040 struct sip_dialog *dialog;
2041 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2042 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2044 if (sbuddy) sbuddy->just_added = FALSE;
2046 if (sip->ocs2007) {
2047 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2048 } else {
2049 autoextend = "Supported: com.microsoft.autoextend\r\n";
2052 request = g_strdup_printf(
2053 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2054 "Supported: ms-piggyback-first-notify\r\n"
2055 "%s%sSupported: ms-benotify\r\n"
2056 "Proxy-Require: ms-benotify\r\n"
2057 "Event: presence\r\n"
2058 "Contact: %s\r\n", autoextend, content_type, tmp);
2060 if (sip->ocs2007) {
2061 content = g_strdup_printf(
2062 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2063 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2064 "<resource uri=\"%s\"%s\n"
2065 "</adhocList>\n"
2066 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2067 "<category name=\"calendarData\"/>\n"
2068 "<category name=\"contactCard\"/>\n"
2069 "<category name=\"note\"/>\n"
2070 "<category name=\"state\"/>\n"
2071 "</categoryList>\n"
2072 "</action>\n"
2073 "</batchSub>", sip->username, to, context);
2076 g_free(tmp);
2078 /* subscribe to buddy presence */
2079 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2080 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2081 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2082 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2084 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2086 g_free(content);
2087 g_free(to);
2088 g_free(request);
2089 g_free(key);
2092 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2094 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2096 if (!purple_status_is_active(status))
2097 return;
2099 if (account->gc) {
2100 struct sipe_account_data *sip = account->gc->proto_data;
2102 if (sip) {
2103 gchar *action_name;
2104 gchar *tmp;
2105 time_t now = time(NULL);
2106 const char *status_id = purple_status_get_id(status);
2107 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2108 sipe_activity activity = sipe_get_activity_by_token(status_id);
2109 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2111 /* when other point of presence clears note, but we are keeping
2112 * state if OOF note.
2114 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2115 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2116 do_not_publish = FALSE;
2119 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2120 status_id, (int)sip->do_not_publish[activity], (int)now);
2122 sip->do_not_publish[activity] = 0;
2123 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2124 status_id, (int)sip->do_not_publish[activity]);
2126 if (do_not_publish)
2128 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2129 return;
2132 g_free(sip->status);
2133 sip->status = g_strdup(status_id);
2135 /* hack to escape apostrof before comparison */
2136 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2138 /* this will preserve OOF flag as well */
2139 if (!sipe_strequal(tmp, sip->note)) {
2140 sip->is_oof_note = FALSE;
2141 g_free(sip->note);
2142 sip->note = g_strdup(note);
2143 sip->note_since = time(NULL);
2145 g_free(tmp);
2147 /* schedule 2 sec to capture idle flag */
2148 action_name = g_strdup_printf("<%s>", "+set-status");
2149 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2150 g_free(action_name);
2154 static void
2155 sipe_set_idle(PurpleConnection * gc,
2156 int interval)
2158 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2160 if (gc) {
2161 struct sipe_account_data *sip = gc->proto_data;
2163 if (sip) {
2164 sip->idle_switch = time(NULL);
2165 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2170 static void
2171 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2172 SIPE_UNUSED_PARAMETER const char *alias)
2174 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2175 sipe_group_set_user(sip, name);
2178 static void
2179 sipe_group_buddy(PurpleConnection *gc,
2180 const char *who,
2181 const char *old_group_name,
2182 const char *new_group_name)
2184 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2185 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2186 struct sipe_group * old_group = NULL;
2187 struct sipe_group * new_group;
2189 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2190 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2192 if(!buddy) { // buddy not in roaming list
2193 return;
2196 if (old_group_name) {
2197 old_group = sipe_group_find_by_name(sip, old_group_name);
2199 new_group = sipe_group_find_by_name(sip, new_group_name);
2201 if (old_group) {
2202 buddy->groups = g_slist_remove(buddy->groups, old_group);
2203 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2206 if (!new_group) {
2207 sipe_group_create(sip, new_group_name, who);
2208 } else {
2209 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2210 sipe_group_set_user(sip, who);
2214 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2216 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2218 /* libpurple can call us with undefined buddy or group */
2219 if (buddy && group) {
2220 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2222 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2223 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2224 purple_blist_rename_buddy(buddy, buddy_name);
2225 g_free(buddy_name);
2227 /* Prepend sip: if needed */
2228 if (!g_str_has_prefix(buddy->name, "sip:")) {
2229 gchar *buf = sip_uri_from_name(buddy->name);
2230 purple_blist_rename_buddy(buddy, buf);
2231 g_free(buf);
2234 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2235 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2236 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2237 b->name = g_strdup(buddy->name);
2238 b->just_added = TRUE;
2239 g_hash_table_insert(sip->buddies, b->name, b);
2240 sipe_group_buddy(gc, b->name, NULL, group->name);
2241 /* @TODO should go to callback */
2242 sipe_subscribe_presence_single(sip, b->name);
2243 } else {
2244 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2249 static void sipe_free_buddy(struct sipe_buddy *buddy)
2251 #ifndef _WIN32
2253 * We are calling g_hash_table_foreach_steal(). That means that no
2254 * key/value deallocation functions are called. Therefore the glib
2255 * hash code does not touch the key (buddy->name) or value (buddy)
2256 * of the to-be-deleted hash node at all. It follows that we
2258 * - MUST free the memory for the key ourselves and
2259 * - ARE allowed to do it in this function
2261 * Conclusion: glib must be broken on the Windows platform if sipe
2262 * crashes with SIGTRAP when closing. You'll have to live
2263 * with the memory leak until this is fixed.
2265 g_free(buddy->name);
2266 #endif
2267 g_free(buddy->activity);
2268 g_free(buddy->meeting_subject);
2269 g_free(buddy->meeting_location);
2270 g_free(buddy->note);
2272 g_free(buddy->cal_start_time);
2273 g_free(buddy->cal_free_busy_base64);
2274 g_free(buddy->cal_free_busy);
2275 g_free(buddy->last_non_cal_activity);
2277 sipe_cal_free_working_hours(buddy->cal_working_hours);
2279 g_free(buddy->device_name);
2280 g_slist_free(buddy->groups);
2281 g_free(buddy);
2285 * Unassociates buddy from group first.
2286 * Then see if no groups left, removes buddy completely.
2287 * Otherwise updates buddy groups on server.
2289 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2291 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2292 struct sipe_buddy *b;
2293 struct sipe_group *g = NULL;
2295 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2296 if (!buddy) return;
2298 b = g_hash_table_lookup(sip->buddies, buddy->name);
2299 if (!b) return;
2301 if (group) {
2302 g = sipe_group_find_by_name(sip, group->name);
2305 if (g) {
2306 b->groups = g_slist_remove(b->groups, g);
2307 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2310 if (g_slist_length(b->groups) < 1) {
2311 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2312 sipe_cancel_scheduled_action(sip, action_name);
2313 g_free(action_name);
2315 g_hash_table_remove(sip->buddies, buddy->name);
2317 if (b->name) {
2318 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2319 send_soap_request(sip, body);
2320 g_free(body);
2323 sipe_free_buddy(b);
2324 } else {
2325 //updates groups on server
2326 sipe_group_set_user(sip, b->name);
2331 static void
2332 sipe_rename_group(PurpleConnection *gc,
2333 const char *old_name,
2334 PurpleGroup *group,
2335 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2337 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2338 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2339 if (s_group) {
2340 sipe_group_rename(sip, s_group, group->name);
2341 } else {
2342 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2346 static void
2347 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2349 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2350 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2351 if (s_group) {
2352 gchar *body;
2353 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2354 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2355 send_soap_request(sip, body);
2356 g_free(body);
2358 sip->groups = g_slist_remove(sip->groups, s_group);
2359 g_free(s_group->name);
2360 g_free(s_group);
2361 } else {
2362 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2366 /** All statuses need message attribute to pass Note */
2367 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2369 PurpleStatusType *type;
2370 GList *types = NULL;
2372 /* Macros to reduce code repetition.
2373 Translators: noun */
2374 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2375 prim, id, name, \
2376 TRUE, user, FALSE, \
2377 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2378 NULL); \
2379 types = g_list_append(types, type);
2381 /* Online */
2382 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2383 NULL,
2384 NULL,
2385 TRUE);
2387 /* Busy */
2388 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2389 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2390 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2391 TRUE);
2393 /* Do Not Disturb */
2394 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2395 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2396 NULL,
2397 TRUE);
2399 /* Away */
2400 /* Goes first in the list as
2401 * purple picks the first status with the AWAY type
2402 * for idle.
2404 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2405 NULL,
2406 NULL,
2407 TRUE);
2409 /* Be Right Back */
2410 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2411 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2412 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2413 TRUE);
2415 /* Appear Offline */
2416 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2417 NULL,
2418 NULL,
2419 TRUE);
2421 /* Offline */
2422 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2423 NULL,
2424 NULL,
2425 TRUE);
2426 types = g_list_append(types, type);
2428 return types;
2432 * A callback for g_hash_table_foreach
2434 static void
2435 sipe_buddy_subscribe_cb(char *buddy_name,
2436 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2437 struct sipe_account_data *sip)
2439 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2440 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2441 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2442 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2444 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2445 g_free(action_name);
2449 * Removes entries from purple buddy list
2450 * that does not correspond ones in the roaming contact list.
2452 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2453 GSList *buddies = purple_find_buddies(sip->account, NULL);
2454 GSList *entry = buddies;
2455 struct sipe_buddy *buddy;
2456 PurpleBuddy *b;
2457 PurpleGroup *g;
2459 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2460 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2461 while (entry) {
2462 b = entry->data;
2463 g = purple_buddy_get_group(b);
2464 buddy = g_hash_table_lookup(sip->buddies, b->name);
2465 if(buddy) {
2466 gboolean in_sipe_groups = FALSE;
2467 GSList *entry2 = buddy->groups;
2468 while (entry2) {
2469 struct sipe_group *group = entry2->data;
2470 if (sipe_strequal(group->name, g->name)) {
2471 in_sipe_groups = TRUE;
2472 break;
2474 entry2 = entry2->next;
2476 if(!in_sipe_groups) {
2477 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2478 purple_blist_remove_buddy(b);
2480 } else {
2481 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2482 purple_blist_remove_buddy(b);
2484 entry = entry->next;
2486 g_slist_free(buddies);
2489 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2491 int len = msg->bodylen;
2493 const gchar *tmp = sipmsg_find_header(msg, "Event");
2494 xmlnode *item;
2495 xmlnode *isc;
2496 const gchar *contacts_delta;
2497 xmlnode *group_node;
2498 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2499 return FALSE;
2502 /* Convert the contact from XML to Purple Buddies */
2503 isc = xmlnode_from_str(msg->body, len);
2504 if (!isc) {
2505 return FALSE;
2508 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2509 if (contacts_delta) {
2510 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2513 if (sipe_strequal(isc->name, "contactList")) {
2515 /* Parse groups */
2516 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2517 struct sipe_group * group = g_new0(struct sipe_group, 1);
2518 const char *name = xmlnode_get_attrib(group_node, "name");
2520 if (g_str_has_prefix(name, "~")) {
2521 name = _("Other Contacts");
2523 group->name = g_strdup(name);
2524 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2526 sipe_group_add(sip, group);
2529 // Make sure we have at least one group
2530 if (g_slist_length(sip->groups) == 0) {
2531 struct sipe_group * group = g_new0(struct sipe_group, 1);
2532 PurpleGroup *purple_group;
2533 group->name = g_strdup(_("Other Contacts"));
2534 group->id = 1;
2535 purple_group = purple_group_new(group->name);
2536 purple_blist_add_group(purple_group, NULL);
2537 sip->groups = g_slist_append(sip->groups, group);
2540 /* Parse contacts */
2541 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2542 const gchar *uri = xmlnode_get_attrib(item, "uri");
2543 const gchar *name = xmlnode_get_attrib(item, "name");
2544 gchar *buddy_name;
2545 struct sipe_buddy *buddy = NULL;
2546 gchar *tmp;
2547 gchar **item_groups;
2548 int i = 0;
2550 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2551 tmp = sip_uri_from_name(uri);
2552 buddy_name = g_ascii_strdown(tmp, -1);
2553 g_free(tmp);
2555 /* assign to group Other Contacts if nothing else received */
2556 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2557 if(is_empty(tmp)) {
2558 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2559 g_free(tmp);
2560 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2562 item_groups = g_strsplit(tmp, " ", 0);
2563 g_free(tmp);
2565 while (item_groups[i]) {
2566 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2568 // If couldn't find the right group for this contact, just put them in the first group we have
2569 if (group == NULL && g_slist_length(sip->groups) > 0) {
2570 group = sip->groups->data;
2573 if (group != NULL) {
2574 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2575 if (!b){
2576 b = purple_buddy_new(sip->account, buddy_name, uri);
2577 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2579 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2582 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2583 if (name != NULL && strlen(name) != 0) {
2584 purple_blist_alias_buddy(b, name);
2586 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2590 if (!buddy) {
2591 buddy = g_new0(struct sipe_buddy, 1);
2592 buddy->name = g_strdup(b->name);
2593 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2596 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2598 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2599 } else {
2600 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2601 name);
2604 i++;
2605 } // while, contact groups
2606 g_strfreev(item_groups);
2607 g_free(buddy_name);
2609 } // for, contacts
2611 sipe_cleanup_local_blist(sip);
2613 /* Add self-contact if not there yet. 2005 systems. */
2614 /* This will resemble subscription to roaming_self in 2007 systems */
2615 if (!sip->ocs2007) {
2616 gchar *self_uri = sip_uri_self(sip);
2617 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2619 if (!buddy) {
2620 buddy = g_new0(struct sipe_buddy, 1);
2621 buddy->name = g_strdup(self_uri);
2622 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2624 g_free(self_uri);
2627 xmlnode_free(isc);
2629 /* subscribe to buddies */
2630 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2631 if (sip->batched_support) {
2632 sipe_subscribe_presence_batched(sip, NULL);
2633 } else {
2634 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2636 sip->subscribed_buddies = TRUE;
2638 /* for 2005 systems schedule contacts' status update
2639 * based on their calendar information
2641 if (!sip->ocs2007) {
2642 sipe_sched_calendar_status_update(sip, time(NULL));
2645 return 0;
2649 * Subscribe roaming contacts
2651 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2653 gchar *to = sip_uri_self(sip);
2654 gchar *tmp = get_contact(sip);
2655 gchar *hdr = g_strdup_printf(
2656 "Event: vnd-microsoft-roaming-contacts\r\n"
2657 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2658 "Supported: com.microsoft.autoextend\r\n"
2659 "Supported: ms-benotify\r\n"
2660 "Proxy-Require: ms-benotify\r\n"
2661 "Supported: ms-piggyback-first-notify\r\n"
2662 "Contact: %s\r\n", tmp);
2663 g_free(tmp);
2665 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2666 g_free(to);
2667 g_free(hdr);
2670 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2671 SIPE_UNUSED_PARAMETER void *unused)
2673 gchar *key;
2674 struct sip_dialog *dialog;
2675 gchar *to = sip_uri_self(sip);
2676 gchar *tmp = get_contact(sip);
2677 gchar *hdr = g_strdup_printf(
2678 "Event: presence.wpending\r\n"
2679 "Accept: text/xml+msrtc.wpending\r\n"
2680 "Supported: com.microsoft.autoextend\r\n"
2681 "Supported: ms-benotify\r\n"
2682 "Proxy-Require: ms-benotify\r\n"
2683 "Supported: ms-piggyback-first-notify\r\n"
2684 "Contact: %s\r\n", tmp);
2685 g_free(tmp);
2687 /* Subscription is identified by <event> key */
2688 key = g_strdup_printf("<%s>", "presence.wpending");
2689 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2690 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2692 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2694 g_free(to);
2695 g_free(hdr);
2696 g_free(key);
2700 * Fires on deregistration event initiated by server.
2701 * [MS-SIPREGE] SIP extension.
2704 // 2007 Example
2706 // Content-Type: text/registration-event
2707 // subscription-state: terminated;expires=0
2708 // ms-diagnostics-public: 4141;reason="User disabled"
2710 // deregistered;event=rejected
2712 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2714 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2715 gchar *event = NULL;
2716 gchar *reason = NULL;
2717 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2718 gchar *warning;
2720 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2721 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2723 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2724 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2725 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2726 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2727 } else {
2728 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2729 return;
2732 if (diagnostics != NULL) {
2733 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2734 } else { // for LCS2005
2735 int error_id = 0;
2736 if (event && sipe_strcase_equal(event, "unregistered")) {
2737 error_id = 4140; // [MS-SIPREGE]
2738 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2739 reason = g_strdup(_("you are already signed in at another location"));
2740 } else if (event && sipe_strcase_equal(event, "rejected")) {
2741 error_id = 4141;
2742 reason = g_strdup(_("user disabled")); // [MS-OCER]
2743 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2744 error_id = 4142;
2745 reason = g_strdup(_("user moved")); // [MS-OCER]
2748 g_free(event);
2749 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2750 g_free(reason);
2752 sip->gc->wants_to_die = TRUE;
2753 purple_connection_error(sip->gc, warning);
2754 g_free(warning);
2758 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2760 xmlnode *xn_provision_group_list;
2761 xmlnode *node;
2763 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2765 /* provisionGroup */
2766 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2767 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2768 g_free(sip->focus_factory_uri);
2769 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2770 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2771 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2772 break;
2775 xmlnode_free(xn_provision_group_list);
2778 /** for 2005 system */
2779 static void
2780 sipe_process_provisioning(struct sipe_account_data *sip,
2781 struct sipmsg *msg)
2783 xmlnode *xn_provision;
2784 xmlnode *node;
2786 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2787 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2788 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2789 if ((node = xmlnode_get_child(node, "line"))) {
2790 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2791 const gchar *server = xmlnode_get_attrib(node, "server");
2792 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2793 sip_csta_open(sip, line_uri, server);
2796 xmlnode_free(xn_provision);
2799 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2801 const gchar *contacts_delta;
2802 xmlnode *xml;
2804 xml = xmlnode_from_str(msg->body, msg->bodylen);
2805 if (!xml)
2807 return;
2810 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2811 if (contacts_delta)
2813 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2816 xmlnode_free(xml);
2819 static void
2820 free_container(struct sipe_container *container)
2822 GSList *entry;
2824 if (!container) return;
2826 entry = container->members;
2827 while (entry) {
2828 void *data = entry->data;
2829 entry = g_slist_remove(entry, data);
2830 g_free(data);
2832 g_free(container);
2836 * Finds locally stored MS-PRES container member
2838 static struct sipe_container_member *
2839 sipe_find_container_member(struct sipe_container *container,
2840 const gchar *type,
2841 const gchar *value)
2843 struct sipe_container_member *member;
2844 GSList *entry;
2846 if (container == NULL || type == NULL) {
2847 return NULL;
2850 entry = container->members;
2851 while (entry) {
2852 member = entry->data;
2853 if (!g_strcasecmp(member->type, type)
2854 && ((!member->value && !value)
2855 || (value && member->value && !g_strcasecmp(member->value, value)))
2857 return member;
2859 entry = entry->next;
2861 return NULL;
2865 * Finds locally stored MS-PRES container by id
2867 static struct sipe_container *
2868 sipe_find_container(struct sipe_account_data *sip,
2869 guint id)
2871 struct sipe_container *container;
2872 GSList *entry;
2874 if (sip == NULL) {
2875 return NULL;
2878 entry = sip->containers;
2879 while (entry) {
2880 container = entry->data;
2881 if (id == container->id) {
2882 return container;
2884 entry = entry->next;
2886 return NULL;
2890 * Access Levels
2891 * 32000 - Blocked
2892 * 400 - Personal
2893 * 300 - Team
2894 * 200 - Company
2895 * 100 - Public
2897 static int
2898 sipe_find_access_level(struct sipe_account_data *sip,
2899 const gchar *type,
2900 const gchar *value)
2902 guint containers[] = {32000, 400, 300, 200, 100};
2903 int i = 0;
2905 for (i = 0; i < 5; i++) {
2906 struct sipe_container_member *member;
2907 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2908 if (!container) continue;
2910 member = sipe_find_container_member(container, type, value);
2911 if (member) {
2912 return containers[i];
2916 return -1;
2919 static void
2920 sipe_send_set_container_members(struct sipe_account_data *sip,
2921 guint container_id,
2922 guint container_version,
2923 const gchar* action,
2924 const gchar* type,
2925 const gchar* value)
2927 gchar *self = sip_uri_self(sip);
2928 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2929 gchar *contact;
2930 gchar *hdr;
2931 gchar *body = g_strdup_printf(
2932 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2933 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2934 "</setContainerMembers>",
2935 container_id,
2936 container_version,
2937 action,
2938 type,
2939 value_str);
2940 g_free(value_str);
2942 contact = get_contact(sip);
2943 hdr = g_strdup_printf("Contact: %s\r\n"
2944 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2945 g_free(contact);
2947 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2949 g_free(hdr);
2950 g_free(body);
2951 g_free(self);
2954 static void
2955 free_publication(struct sipe_publication *publication)
2957 g_free(publication->category);
2958 g_free(publication->cal_event_hash);
2959 g_free(publication->note);
2961 g_free(publication->working_hours_xml_str);
2962 g_free(publication->fb_start_str);
2963 g_free(publication->free_busy_base64);
2965 g_free(publication);
2968 /* key is <category><instance><container> */
2969 static gboolean
2970 sipe_is_our_publication(struct sipe_account_data *sip,
2971 const gchar *key)
2973 GSList *entry;
2975 /* filling keys for our publications if not yet cached */
2976 if (!sip->our_publication_keys) {
2977 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2978 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2979 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2980 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2981 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2982 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2983 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2985 purple_debug_info("sipe", "* Our Publication Instances *\n");
2986 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2987 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2988 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2989 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2990 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2991 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2992 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2993 purple_debug_info("sipe", "\tNote : %u\n", 0);
2994 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2996 /* device */
2997 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2998 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3000 /* state:machineState */
3001 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3002 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3003 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3004 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3006 /* state:userState */
3007 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3008 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3009 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3010 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3012 /* state:calendarState */
3013 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3014 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3015 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3016 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3018 /* state:calendarState OOF */
3019 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3020 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3021 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3022 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3024 /* note */
3025 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3026 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3027 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3028 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3029 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3030 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3032 /* note OOF */
3033 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3034 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3035 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3036 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3037 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3038 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3040 /* calendarData:WorkingHours */
3041 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3042 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3043 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3044 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3045 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3046 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3047 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3048 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3049 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3050 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3051 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3052 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3054 /* calendarData:FreeBusy */
3055 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3056 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3057 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3058 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3059 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3060 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3061 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3062 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3063 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3064 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3065 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3066 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3068 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3069 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3072 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3074 entry = sip->our_publication_keys;
3075 while (entry) {
3076 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3077 if (sipe_strequal(entry->data, key)) {
3078 return TRUE;
3080 entry = entry->next;
3082 return FALSE;
3085 /** Property names to store in blist.xml */
3086 #define ALIAS_PROP "alias"
3087 #define EMAIL_PROP "email"
3088 #define PHONE_PROP "phone"
3089 #define PHONE_DISPLAY_PROP "phone-display"
3090 #define PHONE_MOBILE_PROP "phone-mobile"
3091 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3092 #define PHONE_HOME_PROP "phone-home"
3093 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3094 #define PHONE_OTHER_PROP "phone-other"
3095 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3096 #define PHONE_CUSTOM1_PROP "phone-custom1"
3097 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3098 #define SITE_PROP "site"
3099 #define COMPANY_PROP "company"
3100 #define DEPARTMENT_PROP "department"
3101 #define TITLE_PROP "title"
3102 #define OFFICE_PROP "office"
3103 /** implies work address */
3104 #define ADDRESS_STREET_PROP "address-street"
3105 #define ADDRESS_CITY_PROP "address-city"
3106 #define ADDRESS_STATE_PROP "address-state"
3107 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3108 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3111 * Tries to figure out user first and last name
3112 * based on Display Name and email properties.
3114 * Allocates memory - must be g_free()'d
3116 * Examples to parse:
3117 * First Last
3118 * First Last - Company Name
3119 * Last, First
3120 * Last, First M.
3121 * Last, First (C)(STP) (Company)
3122 * first.last@company.com (preprocessed as "first last")
3123 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3125 * Unusable examples:
3126 * user@company.com (preprocessed as "user")
3127 * first.m.last@company.com (preprocessed as "first m last")
3128 * user.company.com@reuters.net (preprocessed as "user company com")
3130 static void
3131 sipe_get_first_last_names(struct sipe_account_data *sip,
3132 const char *uri,
3133 char **first_name,
3134 char **last_name)
3136 PurpleBuddy *p_buddy;
3137 char *display_name;
3138 const char *email;
3139 const char *first, *last;
3140 char *tmp;
3141 char **parts;
3142 gboolean has_comma = FALSE;
3144 if (!sip || !uri) return;
3146 p_buddy = purple_find_buddy(sip->account, uri);
3148 if (!p_buddy) return;
3150 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3151 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3153 if (!display_name && !email) return;
3155 /* if no display name, make "first last anything_else" out of email */
3156 if (email && !display_name) {
3157 display_name = g_strndup(email, strstr(email, "@") - email);
3158 display_name = purple_strreplace((tmp = display_name), ".", " ");
3159 g_free(tmp);
3162 if (display_name) {
3163 has_comma = (strstr(display_name, ",") != NULL);
3164 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3165 g_free(tmp);
3166 display_name = purple_strreplace((tmp = display_name), ",", " ");
3167 g_free(tmp);
3170 parts = g_strsplit(display_name, " ", 0);
3172 if (!parts[0] || !parts[1]) {
3173 g_free(display_name);
3174 g_strfreev(parts);
3175 return;
3178 if (has_comma) {
3179 last = parts[0];
3180 first = parts[1];
3181 } else {
3182 first = parts[0];
3183 last = parts[1];
3186 if (first_name) {
3187 *first_name = g_strstrip(g_strdup(first));
3190 if (last_name) {
3191 *last_name = g_strstrip(g_strdup(last));
3194 g_free(display_name);
3195 g_strfreev(parts);
3199 * Update user information
3201 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3202 * @param property_name
3203 * @param property_value may be modified to strip white space
3205 static void
3206 sipe_update_user_info(struct sipe_account_data *sip,
3207 const char *uri,
3208 const char *property_name,
3209 char *property_value)
3211 GSList *buddies, *entry;
3213 if (!property_name || strlen(property_name) == 0) return;
3215 if (property_value)
3216 property_value = g_strstrip(property_value);
3218 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3219 while (entry) {
3220 const char *prop_str;
3221 const char *server_alias;
3222 PurpleBuddy *p_buddy = entry->data;
3224 /* for Display Name */
3225 if (sipe_strequal(property_name, ALIAS_PROP)) {
3226 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3227 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3228 purple_blist_alias_buddy(p_buddy, property_value);
3231 server_alias = purple_buddy_get_server_alias(p_buddy);
3232 if (!is_empty(property_value) &&
3233 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3235 purple_blist_server_alias_buddy(p_buddy, property_value);
3238 /* for other properties */
3239 else {
3240 if (!is_empty(property_value)) {
3241 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3242 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3243 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3248 entry = entry->next;
3250 g_slist_free(buddies);
3254 * Update user phone
3255 * Suitable for both 2005 and 2007 systems.
3257 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3258 * @param phone_type
3259 * @param phone may be modified to strip white space
3260 * @param phone_display_string may be modified to strip white space
3262 static void
3263 sipe_update_user_phone(struct sipe_account_data *sip,
3264 const char *uri,
3265 const gchar *phone_type,
3266 gchar *phone,
3267 gchar *phone_display_string)
3269 const char *phone_node = PHONE_PROP; /* work phone by default */
3270 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3272 if(!phone || strlen(phone) == 0) return;
3274 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3275 phone_node = PHONE_MOBILE_PROP;
3276 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3277 } else if (sipe_strequal(phone_type, "home")) {
3278 phone_node = PHONE_HOME_PROP;
3279 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3280 } else if (sipe_strequal(phone_type, "other")) {
3281 phone_node = PHONE_OTHER_PROP;
3282 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3283 } else if (sipe_strequal(phone_type, "custom1")) {
3284 phone_node = PHONE_CUSTOM1_PROP;
3285 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3288 sipe_update_user_info(sip, uri, phone_node, phone);
3289 if (phone_display_string) {
3290 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3294 static void
3295 sipe_update_calendar(struct sipe_account_data *sip)
3297 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3299 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3301 if (sipe_strequal(calendar, "EXCH")) {
3302 sipe_ews_update_calendar(sip);
3305 /* schedule repeat */
3306 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3308 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3312 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3313 * by using standard Purple's means of signals and saved statuses.
3315 * Thus all UI elements get updated: Status Button with Note, docklet.
3316 * This is ablolutely important as both our status and note can come
3317 * inbound (roaming) or be updated programmatically (e.g. based on our
3318 * calendar data).
3320 static void
3321 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3322 const char *status_id,
3323 const char *message,
3324 time_t do_not_publish[])
3326 PurpleStatus *status = purple_account_get_active_status(account);
3327 gboolean changed = TRUE;
3329 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3330 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3332 changed = FALSE;
3335 if (purple_savedstatus_is_idleaway()) {
3336 changed = FALSE;
3339 if (changed) {
3340 PurpleSavedStatus *saved_status;
3341 const PurpleStatusType *acct_status_type =
3342 purple_status_type_find_with_id(account->status_types, status_id);
3343 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3344 sipe_activity activity = sipe_get_activity_by_token(status_id);
3346 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3347 if (saved_status) {
3348 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3351 /* If this type+message is unique then create a new transient saved status
3352 * Ref: gtkstatusbox.c
3354 if (!saved_status) {
3355 GList *tmp;
3356 GList *active_accts = purple_accounts_get_all_active();
3358 saved_status = purple_savedstatus_new(NULL, primitive);
3359 purple_savedstatus_set_message(saved_status, message);
3361 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3362 purple_savedstatus_set_substatus(saved_status,
3363 (PurpleAccount *)tmp->data, acct_status_type, message);
3365 g_list_free(active_accts);
3368 do_not_publish[activity] = time(NULL);
3369 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3370 status_id, (int)do_not_publish[activity]);
3372 /* Set the status for each account */
3373 purple_savedstatus_activate(saved_status);
3377 struct hash_table_delete_payload {
3378 GHashTable *hash_table;
3379 guint container;
3382 static void
3383 sipe_remove_category_container_publications_cb(const char *name,
3384 struct sipe_publication *publication,
3385 struct hash_table_delete_payload *payload)
3387 if (publication->container == payload->container) {
3388 g_hash_table_remove(payload->hash_table, name);
3391 static void
3392 sipe_remove_category_container_publications(GHashTable *our_publications,
3393 const char *category,
3394 guint container)
3396 struct hash_table_delete_payload payload;
3397 payload.hash_table = g_hash_table_lookup(our_publications, category);
3399 if (!payload.hash_table) return;
3401 payload.container = container;
3402 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3405 static void
3406 send_publish_category_initial(struct sipe_account_data *sip);
3409 * When we receive some self (BE) NOTIFY with a new subscriber
3410 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3413 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3415 gchar *contact;
3416 gchar *to;
3417 xmlnode *xml;
3418 xmlnode *node;
3419 xmlnode *node2;
3420 char *display_name = NULL;
3421 char *uri;
3422 GSList *category_names = NULL;
3423 int aggreg_avail = 0;
3424 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3425 gboolean do_update_status = FALSE;
3426 gboolean has_note_cleaned = FALSE;
3428 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3430 xml = xmlnode_from_str(msg->body, msg->bodylen);
3431 if (!xml) return;
3433 contact = get_contact(sip);
3434 to = sip_uri_self(sip);
3437 /* categories */
3438 /* set list of categories participating in this XML */
3439 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3440 const gchar *name = xmlnode_get_attrib(node, "name");
3441 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3443 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3444 category_names ? (int) g_slist_length(category_names) : -1);
3445 /* drop category information */
3446 if (category_names) {
3447 GSList *entry = category_names;
3448 while (entry) {
3449 GHashTable *cat_publications;
3450 const gchar *category = entry->data;
3451 entry = entry->next;
3452 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3453 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3454 if (cat_publications) {
3455 g_hash_table_remove(sip->our_publications, category);
3456 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3460 g_slist_free(category_names);
3461 /* filling our categories reflected in roaming data */
3462 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3463 const char *tmp;
3464 const gchar *name = xmlnode_get_attrib(node, "name");
3465 guint container = xmlnode_get_int_attrib(node, "container", -1);
3466 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3467 guint version = xmlnode_get_int_attrib(node, "version", 0);
3468 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3469 sipe_utils_str_to_time(tmp) : 0;
3470 gchar *key;
3471 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3473 /* Ex. clear note: <category name="note"/> */
3474 if (container == (guint)-1) {
3475 g_free(sip->note);
3476 sip->note = NULL;
3477 do_update_status = TRUE;
3478 continue;
3481 /* Ex. clear note: <category name="note" container="200"/> */
3482 if (instance == (guint)-1) {
3483 if (container == 200) {
3484 g_free(sip->note);
3485 sip->note = NULL;
3486 do_update_status = TRUE;
3488 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3489 sipe_remove_category_container_publications(
3490 sip->our_publications, name, container);
3491 continue;
3494 /* key is <category><instance><container> */
3495 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3496 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3498 /* capture all userState publication for later clean up if required */
3499 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3500 xmlnode *xn_state = xmlnode_get_child(node, "state");
3502 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3503 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3504 publication->category = g_strdup(name);
3505 publication->instance = instance;
3506 publication->container = container;
3507 publication->version = version;
3509 if (!sip->user_state_publications) {
3510 sip->user_state_publications = g_hash_table_new_full(
3511 g_str_hash, g_str_equal,
3512 g_free, (GDestroyNotify)free_publication);
3514 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3515 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3516 key, version);
3520 if (sipe_is_our_publication(sip, key)) {
3521 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3523 publication->category = g_strdup(name);
3524 publication->instance = instance;
3525 publication->container = container;
3526 publication->version = version;
3528 /* filling publication->availability */
3529 if (sipe_strequal(name, "state")) {
3530 xmlnode *xn_state = xmlnode_get_child(node, "state");
3531 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3533 if (xn_avail) {
3534 gchar *avail_str = xmlnode_get_data(xn_avail);
3535 if (avail_str) {
3536 publication->availability = atoi(avail_str);
3538 g_free(avail_str);
3540 /* for calendarState */
3541 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3542 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3543 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3545 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3546 if (xn_activity) {
3547 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3548 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3550 event->is_meeting = TRUE;
3553 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3554 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3556 publication->cal_event_hash = sipe_cal_event_hash(event);
3557 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3558 publication->cal_event_hash);
3559 sipe_cal_event_free(event);
3562 /* filling publication->note */
3563 if (sipe_strequal(name, "note")) {
3564 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3566 if (!has_note_cleaned) {
3567 has_note_cleaned = TRUE;
3569 g_free(sip->note);
3570 sip->note = NULL;
3571 sip->note_since = publish_time;
3573 do_update_status = TRUE;
3576 g_free(publication->note);
3577 publication->note = NULL;
3578 if (xn_body) {
3579 char *tmp;
3581 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3582 g_free(tmp);
3583 if (publish_time >= sip->note_since) {
3584 g_free(sip->note);
3585 sip->note = g_strdup(publication->note);
3586 sip->note_since = publish_time;
3587 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3589 do_update_status = TRUE;
3594 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3595 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3596 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3597 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3598 if (xn_free_busy) {
3599 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3600 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3602 if (xn_working_hours) {
3603 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3607 if (!cat_publications) {
3608 cat_publications = g_hash_table_new_full(
3609 g_str_hash, g_str_equal,
3610 g_free, (GDestroyNotify)free_publication);
3611 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3612 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3614 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3615 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3617 g_free(key);
3619 /* aggregateState (not an our publication) from 2-nd container */
3620 if (sipe_strequal(name, "state") && container == 2) {
3621 xmlnode *xn_state = xmlnode_get_child(node, "state");
3623 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3624 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3625 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3627 if (xn_avail) {
3628 gchar *avail_str = xmlnode_get_data(xn_avail);
3629 if (avail_str) {
3630 aggreg_avail = atoi(avail_str);
3632 g_free(avail_str);
3635 if (xn_activity) {
3636 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3638 aggreg_activity = sipe_get_activity_by_token(activity_token);
3641 do_update_status = TRUE;
3645 /* userProperties published by server from AD */
3646 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3647 xmlnode *line;
3648 /* line, for Remote Call Control (RCC) */
3649 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3650 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3651 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3652 gchar *line_uri;
3654 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3656 line_uri = xmlnode_get_data(line);
3657 if (line_uri) {
3658 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3659 sip_csta_open(sip, line_uri, line_server);
3661 g_free(line_uri);
3663 break;
3667 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3668 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3670 /* containers */
3671 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3672 guint id = xmlnode_get_int_attrib(node, "id", 0);
3673 struct sipe_container *container = sipe_find_container(sip, id);
3675 if (container) {
3676 sip->containers = g_slist_remove(sip->containers, container);
3677 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3678 free_container(container);
3680 container = g_new0(struct sipe_container, 1);
3681 container->id = id;
3682 container->version = xmlnode_get_int_attrib(node, "version", 0);
3683 sip->containers = g_slist_append(sip->containers, container);
3684 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3686 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3687 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3688 member->type = xmlnode_get_attrib(node2, "type");
3689 member->value = xmlnode_get_attrib(node2, "value");
3690 container->members = g_slist_append(container->members, member);
3691 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3692 member->type, member->value ? member->value : "");
3696 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3697 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3698 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3699 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3700 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3701 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3702 /* initial set-up to let counterparties see your status */
3703 if (sameEnterpriseAL < 0) {
3704 struct sipe_container *container = sipe_find_container(sip, 200);
3705 guint version = container ? container->version : 0;
3706 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3708 if (federatedAL < 0) {
3709 struct sipe_container *container = sipe_find_container(sip, 100);
3710 guint version = container ? container->version : 0;
3711 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3713 sip->access_level_set = TRUE;
3716 /* subscribers */
3717 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3718 const char *user;
3719 const char *acknowledged;
3720 gchar *hdr;
3721 gchar *body;
3723 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3724 if (!user) continue;
3725 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3726 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3727 uri = sip_uri_from_name(user);
3729 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3731 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3732 if(sipe_strcase_equal(acknowledged,"false")){
3733 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3734 if (!purple_find_buddy(sip->account, uri)) {
3735 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3738 hdr = g_strdup_printf(
3739 "Contact: %s\r\n"
3740 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3742 body = g_strdup_printf(
3743 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3744 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3745 "</setSubscribers>", user);
3747 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3748 g_free(body);
3749 g_free(hdr);
3751 g_free(display_name);
3752 g_free(uri);
3755 g_free(contact);
3756 xmlnode_free(xml);
3758 /* Publish initial state if not yet.
3759 * Assuming this happens on initial responce to subscription to roaming-self
3760 * so we've already updated our roaming data in full.
3761 * Only for 2007+
3763 if (!sip->initial_state_published) {
3764 send_publish_category_initial(sip);
3765 sip->initial_state_published = TRUE;
3766 /* dalayed run */
3767 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3768 do_update_status = FALSE;
3769 } else if (aggreg_avail) {
3771 g_free(sip->status);
3772 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3773 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3774 } else {
3775 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3779 if (do_update_status) {
3780 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3781 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3784 g_free(to);
3787 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3789 gchar *to = sip_uri_self(sip);
3790 gchar *tmp = get_contact(sip);
3791 gchar *hdr = g_strdup_printf(
3792 "Event: vnd-microsoft-roaming-ACL\r\n"
3793 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3794 "Supported: com.microsoft.autoextend\r\n"
3795 "Supported: ms-benotify\r\n"
3796 "Proxy-Require: ms-benotify\r\n"
3797 "Supported: ms-piggyback-first-notify\r\n"
3798 "Contact: %s\r\n", tmp);
3799 g_free(tmp);
3801 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3802 g_free(to);
3803 g_free(hdr);
3807 * To request for presence information about the user, access level settings that have already been configured by the user
3808 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3809 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3812 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3814 gchar *to = sip_uri_self(sip);
3815 gchar *tmp = get_contact(sip);
3816 gchar *hdr = g_strdup_printf(
3817 "Event: vnd-microsoft-roaming-self\r\n"
3818 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3819 "Supported: ms-benotify\r\n"
3820 "Proxy-Require: ms-benotify\r\n"
3821 "Supported: ms-piggyback-first-notify\r\n"
3822 "Contact: %s\r\n"
3823 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3825 gchar *body=g_strdup(
3826 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3827 "<roaming type=\"categories\"/>"
3828 "<roaming type=\"containers\"/>"
3829 "<roaming type=\"subscribers\"/></roamingList>");
3831 g_free(tmp);
3832 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3833 g_free(body);
3834 g_free(to);
3835 g_free(hdr);
3839 * For 2005 version
3841 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3843 gchar *to = sip_uri_self(sip);
3844 gchar *tmp = get_contact(sip);
3845 gchar *hdr = g_strdup_printf(
3846 "Event: vnd-microsoft-provisioning\r\n"
3847 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3848 "Supported: com.microsoft.autoextend\r\n"
3849 "Supported: ms-benotify\r\n"
3850 "Proxy-Require: ms-benotify\r\n"
3851 "Supported: ms-piggyback-first-notify\r\n"
3852 "Expires: 0\r\n"
3853 "Contact: %s\r\n", tmp);
3855 g_free(tmp);
3856 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3857 g_free(to);
3858 g_free(hdr);
3861 /** Subscription for provisioning information to help with initial
3862 * configuration. This subscription is a one-time query (denoted by the Expires header,
3863 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3864 * configuration, meeting policies, and policy settings that Communicator must enforce.
3865 * TODO: for what we need this information.
3868 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3870 gchar *to = sip_uri_self(sip);
3871 gchar *tmp = get_contact(sip);
3872 gchar *hdr = g_strdup_printf(
3873 "Event: vnd-microsoft-provisioning-v2\r\n"
3874 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3875 "Supported: com.microsoft.autoextend\r\n"
3876 "Supported: ms-benotify\r\n"
3877 "Proxy-Require: ms-benotify\r\n"
3878 "Supported: ms-piggyback-first-notify\r\n"
3879 "Expires: 0\r\n"
3880 "Contact: %s\r\n"
3881 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3882 gchar *body = g_strdup(
3883 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3884 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3885 "<provisioningGroup name=\"ucPolicy\"/>"
3886 "</provisioningGroupList>");
3888 g_free(tmp);
3889 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3890 g_free(body);
3891 g_free(to);
3892 g_free(hdr);
3895 static void
3896 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3897 gpointer value, gpointer user_data)
3899 struct sip_subscription *subscription = value;
3900 struct sip_dialog *dialog = &subscription->dialog;
3901 struct sipe_account_data *sip = user_data;
3902 gchar *tmp = get_contact(sip);
3903 gchar *hdr = g_strdup_printf(
3904 "Event: %s\r\n"
3905 "Expires: 0\r\n"
3906 "Contact: %s\r\n", subscription->event, tmp);
3907 g_free(tmp);
3909 /* Rate limit to max. 25 requests per seconds */
3910 g_usleep(1000000 / 25);
3912 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3913 g_free(hdr);
3916 /* IM Session (INVITE and MESSAGE methods) */
3918 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3919 static gchar *
3920 get_end_points (struct sipe_account_data *sip,
3921 struct sip_session *session)
3923 gchar *res;
3925 if (session == NULL) {
3926 return NULL;
3929 res = g_strdup_printf("<sip:%s>", sip->username);
3931 SIPE_DIALOG_FOREACH {
3932 gchar *tmp = res;
3933 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3934 g_free(tmp);
3936 if (dialog->theirepid) {
3937 tmp = res;
3938 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3939 g_free(tmp);
3941 } SIPE_DIALOG_FOREACH_END;
3943 return res;
3946 static gboolean
3947 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3948 struct sipmsg *msg,
3949 SIPE_UNUSED_PARAMETER struct transaction *trans)
3951 gboolean ret = TRUE;
3953 if (msg->response != 200) {
3954 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3955 return FALSE;
3958 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3960 return ret;
3964 * Asks UA/proxy about its capabilities.
3966 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3968 gchar *to = sip_uri(who);
3969 gchar *contact = get_contact(sip);
3970 gchar *request = g_strdup_printf(
3971 "Accept: application/sdp\r\n"
3972 "Contact: %s\r\n", contact);
3973 g_free(contact);
3975 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3977 g_free(to);
3978 g_free(request);
3981 static void
3982 sipe_notify_user(struct sipe_account_data *sip,
3983 struct sip_session *session,
3984 PurpleMessageFlags flags,
3985 const gchar *message)
3987 PurpleConversation *conv;
3989 if (!session->conv) {
3990 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3991 } else {
3992 conv = session->conv;
3994 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3997 void
3998 sipe_present_info(struct sipe_account_data *sip,
3999 struct sip_session *session,
4000 const gchar *message)
4002 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4005 static void
4006 sipe_present_err(struct sipe_account_data *sip,
4007 struct sip_session *session,
4008 const gchar *message)
4010 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4013 void
4014 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4015 struct sip_session *session,
4016 int sip_error,
4017 int sip_warning,
4018 const gchar *who,
4019 const gchar *message)
4021 char *msg, *msg_tmp, *msg_tmp2;
4022 const char *label;
4024 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4025 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4026 g_free(msg_tmp);
4027 /* Service unavailable; Server Internal Error; Server Time-out */
4028 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4029 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4030 g_free(msg);
4031 msg = NULL;
4032 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4033 label = _("This message was not delivered to %s because the service is not available");
4034 } else if (sip_error == 486) { /* Busy Here */
4035 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4036 } else if (sip_error == 415) { /* Unsupported media type */
4037 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4038 } else {
4039 label = _("This message was not delivered to %s because one or more recipients are offline");
4042 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4043 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4044 msg ? ":" : "",
4045 msg ? msg : "");
4046 sipe_present_err(sip, session, msg_tmp);
4047 g_free(msg_tmp2);
4048 g_free(msg_tmp);
4049 g_free(msg);
4053 static gboolean
4054 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4055 SIPE_UNUSED_PARAMETER struct transaction *trans)
4057 gboolean ret = TRUE;
4058 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4059 struct sip_session *session = sipe_session_find_im(sip, with);
4060 struct sip_dialog *dialog;
4061 gchar *cseq;
4062 char *key;
4063 struct queued_message *message;
4065 if (!session) {
4066 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4067 g_free(with);
4068 return FALSE;
4071 dialog = sipe_dialog_find(session, with);
4072 if (!dialog) {
4073 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4074 g_free(with);
4075 return FALSE;
4078 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4079 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4080 g_free(cseq);
4081 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4083 if (msg->response >= 400) {
4084 PurpleBuddy *pbuddy;
4085 const char *alias = with;
4086 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4087 int warning = -1;
4089 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4091 if (warn_hdr) {
4092 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4093 if (parts[0]) {
4094 warning = atoi(parts[0]);
4096 g_strfreev(parts);
4099 /* cancel file transfer as rejected by server */
4100 if (msg->response == 606 && /* Not acceptable all. */
4101 warning == 309 && /* Message contents not allowed by policy */
4102 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4104 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4105 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4106 sipe_utils_nameval_free(parsed_body);
4109 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4110 alias = purple_buddy_get_alias(pbuddy);
4113 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4115 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4116 if (msg->response == 408 || /* Request timeout */
4117 msg->response == 480 || /* Temporarily Unavailable */
4118 msg->response == 481) { /* Call/Transaction Does Not Exist */
4119 purple_debug_info("sipe", "process_message_response: assuming dangling IM session, dropping it.\n");
4120 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4123 ret = FALSE;
4124 } else {
4125 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4126 if (message_id) {
4127 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4128 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4129 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4132 g_hash_table_remove(session->unconfirmed_messages, key);
4133 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4134 key, g_hash_table_size(session->unconfirmed_messages));
4137 g_free(key);
4138 g_free(with);
4140 if (ret) sipe_im_process_queue(sip, session);
4141 return ret;
4144 static gboolean
4145 sipe_is_election_finished(struct sip_session *session);
4147 static void
4148 sipe_election_result(struct sipe_account_data *sip,
4149 void *sess);
4151 static gboolean
4152 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4153 SIPE_UNUSED_PARAMETER struct transaction *trans)
4155 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4156 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4157 struct sip_dialog *dialog;
4158 struct sip_session *session;
4160 session = sipe_session_find_chat_by_callid(sip, callid);
4161 if (!session) {
4162 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4163 return FALSE;
4166 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4167 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4168 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4169 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4171 if (xn_request_rm_response) {
4172 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4173 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4175 dialog = sipe_dialog_find(session, with);
4176 if (!dialog) {
4177 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4178 xmlnode_free(xn_action);
4179 return FALSE;
4182 if (allow && !g_strcasecmp(allow, "true")) {
4183 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4184 dialog->election_vote = 1;
4185 } else if (allow && !g_strcasecmp(allow, "false")) {
4186 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4187 dialog->election_vote = -1;
4190 if (sipe_is_election_finished(session)) {
4191 sipe_election_result(sip, session);
4194 } else if (xn_set_rm_response) {
4197 xmlnode_free(xn_action);
4201 return TRUE;
4204 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4206 gchar *hdr;
4207 gchar *tmp;
4208 char *msgtext = NULL;
4209 const gchar *msgr = "";
4210 gchar *tmp2 = NULL;
4212 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4213 char *msgformat;
4214 gchar *msgr_value;
4216 sipe_parse_html(msg, &msgformat, &msgtext);
4217 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4219 msgr_value = sipmsg_get_msgr_string(msgformat);
4220 g_free(msgformat);
4221 if (msgr_value) {
4222 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4223 g_free(msgr_value);
4225 } else {
4226 msgtext = g_strdup(msg);
4229 tmp = get_contact(sip);
4230 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4231 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4232 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4233 if (content_type == NULL)
4234 content_type = "text/plain";
4236 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4237 g_free(tmp);
4238 g_free(tmp2);
4240 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4241 g_free(msgtext);
4242 g_free(hdr);
4246 void
4247 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4249 GSList *entry2 = session->outgoing_message_queue;
4250 while (entry2) {
4251 struct queued_message *msg = entry2->data;
4253 /* for multiparty chat or conference */
4254 if (session->is_multiparty || session->focus_uri) {
4255 gchar *who = sip_uri_self(sip);
4256 serv_got_chat_in(sip->gc, session->chat_id, who,
4257 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4258 g_free(who);
4261 SIPE_DIALOG_FOREACH {
4262 char *key;
4263 struct queued_message *message;
4265 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4267 message = g_new0(struct queued_message,1);
4268 message->body = g_strdup(msg->body);
4269 if (msg->content_type != NULL)
4270 message->content_type = g_strdup(msg->content_type);
4272 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4273 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4274 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4275 key, g_hash_table_size(session->unconfirmed_messages));
4276 g_free(key);
4278 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4279 } SIPE_DIALOG_FOREACH_END;
4281 entry2 = sipe_session_dequeue_message(session);
4285 static void
4286 sipe_refer_notify(struct sipe_account_data *sip,
4287 struct sip_session *session,
4288 const gchar *who,
4289 int status,
4290 const gchar *desc)
4292 gchar *hdr;
4293 gchar *body;
4294 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4296 hdr = g_strdup_printf(
4297 "Event: refer\r\n"
4298 "Subscription-State: %s\r\n"
4299 "Content-Type: message/sipfrag\r\n",
4300 status >= 200 ? "terminated" : "active");
4302 body = g_strdup_printf(
4303 "SIP/2.0 %d %s\r\n",
4304 status, desc);
4306 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4308 g_free(hdr);
4309 g_free(body);
4312 static gboolean
4313 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4315 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4316 struct sip_session *session;
4317 struct sip_dialog *dialog;
4318 char *cseq;
4319 char *key;
4320 struct queued_message *message;
4321 struct sipmsg *request_msg = trans->msg;
4323 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4324 gchar *referred_by;
4326 session = sipe_session_find_chat_by_callid(sip, callid);
4327 if (!session) {
4328 session = sipe_session_find_im(sip, with);
4330 if (!session) {
4331 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4332 g_free(with);
4333 return FALSE;
4336 dialog = sipe_dialog_find(session, with);
4337 if (!dialog) {
4338 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4339 g_free(with);
4340 return FALSE;
4343 sipe_dialog_parse(dialog, msg, TRUE);
4345 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4346 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4347 g_free(cseq);
4348 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4350 if (msg->response != 200) {
4351 PurpleBuddy *pbuddy;
4352 const char *alias = with;
4353 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4354 int warning = -1;
4356 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4358 if (warn_hdr) {
4359 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4360 if (parts[0]) {
4361 warning = atoi(parts[0]);
4363 g_strfreev(parts);
4366 /* cancel file transfer as rejected by server */
4367 if (msg->response == 606 && /* Not acceptable all. */
4368 warning == 309 && /* Message contents not allowed by policy */
4369 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4371 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4372 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4373 sipe_utils_nameval_free(parsed_body);
4376 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4377 alias = purple_buddy_get_alias(pbuddy);
4380 if (message) {
4381 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4382 } else {
4383 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4384 sipe_present_err(sip, session, tmp_msg);
4385 g_free(tmp_msg);
4388 sipe_dialog_remove(session, with);
4390 g_free(key);
4391 g_free(with);
4392 return FALSE;
4395 dialog->cseq = 0;
4396 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4397 dialog->outgoing_invite = NULL;
4398 dialog->is_established = TRUE;
4400 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4401 if (referred_by) {
4402 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4403 g_free(referred_by);
4406 /* add user to chat if it is a multiparty session */
4407 if (session->is_multiparty) {
4408 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4409 with, NULL,
4410 PURPLE_CBFLAGS_NONE, TRUE);
4413 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4414 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4415 sipe_session_dequeue_message(session);
4418 sipe_im_process_queue(sip, session);
4420 g_hash_table_remove(session->unconfirmed_messages, key);
4421 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4422 key, g_hash_table_size(session->unconfirmed_messages));
4424 g_free(key);
4425 g_free(with);
4426 return TRUE;
4430 void
4431 sipe_invite(struct sipe_account_data *sip,
4432 struct sip_session *session,
4433 const gchar *who,
4434 const gchar *msg_body,
4435 const gchar *msg_content_type,
4436 const gchar *referred_by,
4437 const gboolean is_triggered)
4439 gchar *hdr;
4440 gchar *to;
4441 gchar *contact;
4442 gchar *body;
4443 gchar *self;
4444 char *ms_text_format = NULL;
4445 gchar *roster_manager;
4446 gchar *end_points;
4447 gchar *referred_by_str;
4448 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4450 if (dialog && dialog->is_established) {
4451 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4452 return;
4455 if (!dialog) {
4456 dialog = sipe_dialog_add(session);
4457 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4458 dialog->with = g_strdup(who);
4461 if (!(dialog->ourtag)) {
4462 dialog->ourtag = gentag();
4465 to = sip_uri(who);
4467 if (msg_body) {
4468 char *msgtext = NULL;
4469 char *base64_msg;
4470 const gchar *msgr = "";
4471 char *key;
4472 struct queued_message *message;
4473 gchar *tmp = NULL;
4475 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4476 char *msgformat;
4477 gchar *msgr_value;
4479 sipe_parse_html(msg_body, &msgformat, &msgtext);
4480 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4482 msgr_value = sipmsg_get_msgr_string(msgformat);
4483 g_free(msgformat);
4484 if (msgr_value) {
4485 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4486 g_free(msgr_value);
4488 } else {
4489 msgtext = g_strdup(msg_body);
4492 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4493 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4494 msg_content_type ? msg_content_type : "text/plain",
4495 msgr,
4496 base64_msg);
4497 g_free(msgtext);
4498 g_free(tmp);
4499 g_free(base64_msg);
4501 message = g_new0(struct queued_message,1);
4502 message->body = g_strdup(msg_body);
4503 if (msg_content_type != NULL)
4504 message->content_type = g_strdup(msg_content_type);
4506 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4507 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4508 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4509 key, g_hash_table_size(session->unconfirmed_messages));
4510 g_free(key);
4513 contact = get_contact(sip);
4514 end_points = get_end_points(sip, session);
4515 self = sip_uri_self(sip);
4516 roster_manager = g_strdup_printf(
4517 "Roster-Manager: %s\r\n"
4518 "EndPoints: %s\r\n",
4519 self,
4520 end_points);
4521 referred_by_str = referred_by ?
4522 g_strdup_printf(
4523 "Referred-By: %s\r\n",
4524 referred_by)
4525 : g_strdup("");
4526 hdr = g_strdup_printf(
4527 "Supported: ms-sender\r\n"
4528 "%s"
4529 "%s"
4530 "%s"
4531 "%s"
4532 "Contact: %s\r\n%s"
4533 "Content-Type: application/sdp\r\n",
4534 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4535 referred_by_str,
4536 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4537 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4538 contact,
4539 ms_text_format ? ms_text_format : "");
4540 g_free(ms_text_format);
4541 g_free(self);
4543 body = g_strdup_printf(
4544 "v=0\r\n"
4545 "o=- 0 0 IN IP4 %s\r\n"
4546 "s=session\r\n"
4547 "c=IN IP4 %s\r\n"
4548 "t=0 0\r\n"
4549 "m=%s %d sip null\r\n"
4550 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4551 purple_network_get_my_ip(-1),
4552 purple_network_get_my_ip(-1),
4553 sip->ocs2007 ? "message" : "x-ms-message",
4554 sip->realport);
4556 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4557 to, to, hdr, body, dialog, process_invite_response);
4559 g_free(to);
4560 g_free(roster_manager);
4561 g_free(end_points);
4562 g_free(referred_by_str);
4563 g_free(body);
4564 g_free(hdr);
4565 g_free(contact);
4568 static void
4569 sipe_refer(struct sipe_account_data *sip,
4570 struct sip_session *session,
4571 const gchar *who)
4573 gchar *hdr;
4574 gchar *contact;
4575 gchar *epid = get_epid(sip);
4576 struct sip_dialog *dialog = sipe_dialog_find(session,
4577 session->roster_manager);
4578 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4580 contact = get_contact(sip);
4581 hdr = g_strdup_printf(
4582 "Contact: %s\r\n"
4583 "Refer-to: <%s>\r\n"
4584 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4585 "Require: com.microsoft.rtc-multiparty\r\n",
4586 contact,
4587 who,
4588 sip->username,
4589 ourtag ? ";tag=" : "",
4590 ourtag ? ourtag : "",
4591 epid);
4592 g_free(epid);
4594 send_sip_request(sip->gc, "REFER",
4595 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4597 g_free(hdr);
4598 g_free(contact);
4601 static void
4602 sipe_send_election_request_rm(struct sipe_account_data *sip,
4603 struct sip_dialog *dialog,
4604 int bid)
4606 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4608 gchar *body = g_strdup_printf(
4609 "<?xml version=\"1.0\"?>\r\n"
4610 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4611 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4612 sip->username, bid);
4614 send_sip_request(sip->gc, "INFO",
4615 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4617 g_free(body);
4620 static void
4621 sipe_send_election_set_rm(struct sipe_account_data *sip,
4622 struct sip_dialog *dialog)
4624 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4626 gchar *body = g_strdup_printf(
4627 "<?xml version=\"1.0\"?>\r\n"
4628 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4629 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4630 sip->username);
4632 send_sip_request(sip->gc, "INFO",
4633 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4635 g_free(body);
4638 static void
4639 sipe_session_close(struct sipe_account_data *sip,
4640 struct sip_session * session)
4642 if (session && session->focus_uri) {
4643 sipe_conf_immcu_closed(sip, session);
4644 conf_session_close(sip, session);
4647 if (session) {
4648 SIPE_DIALOG_FOREACH {
4649 /* @TODO slow down BYE message sending rate */
4650 /* @see single subscription code */
4651 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4652 } SIPE_DIALOG_FOREACH_END;
4654 sipe_session_remove(sip, session);
4658 static void
4659 sipe_session_close_all(struct sipe_account_data *sip)
4661 GSList *entry;
4662 while ((entry = sip->sessions) != NULL) {
4663 sipe_session_close(sip, entry->data);
4667 static void
4668 sipe_convo_closed(PurpleConnection * gc, const char *who)
4670 struct sipe_account_data *sip = gc->proto_data;
4672 purple_debug_info("sipe", "conversation with %s closed\n", who);
4673 sipe_session_close(sip, sipe_session_find_im(sip, who));
4676 static void
4677 sipe_chat_invite(PurpleConnection *gc, int id,
4678 SIPE_UNUSED_PARAMETER const char *message,
4679 const char *name)
4681 sipe_chat_create(gc->proto_data, id, name);
4684 static void
4685 sipe_chat_leave (PurpleConnection *gc, int id)
4687 struct sipe_account_data *sip = gc->proto_data;
4688 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4690 sipe_session_close(sip, session);
4693 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4694 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4696 struct sipe_account_data *sip = gc->proto_data;
4697 struct sip_session *session;
4698 struct sip_dialog *dialog;
4699 gchar *uri = sip_uri(who);
4701 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4703 session = sipe_session_find_or_add_im(sip, uri);
4704 dialog = sipe_dialog_find(session, uri);
4706 // Queue the message
4707 sipe_session_enqueue_message(session, what, NULL);
4709 if (dialog && !dialog->outgoing_invite) {
4710 sipe_im_process_queue(sip, session);
4711 } else if (!dialog || !dialog->outgoing_invite) {
4712 // Need to send the INVITE to get the outgoing dialog setup
4713 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4716 g_free(uri);
4717 return 1;
4720 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4721 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4723 struct sipe_account_data *sip = gc->proto_data;
4724 struct sip_session *session;
4726 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4728 session = sipe_session_find_chat_by_id(sip, id);
4730 // Queue the message
4731 if (session && session->dialogs) {
4732 sipe_session_enqueue_message(session,what,NULL);
4733 sipe_im_process_queue(sip, session);
4734 } else if (sip) {
4735 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4736 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4738 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4739 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4741 if (sip->ocs2007) {
4742 struct sip_session *session = sipe_session_add_chat(sip);
4744 session->is_multiparty = FALSE;
4745 session->focus_uri = g_strdup(proto_chat_id);
4746 sipe_session_enqueue_message(session, what, NULL);
4747 sipe_invite_conf_focus(sip, session);
4751 return 1;
4754 /* End IM Session (INVITE and MESSAGE methods) */
4756 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4758 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4759 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4760 gchar *from;
4761 struct sip_session *session;
4763 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4765 /* Call Control protocol */
4766 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4768 process_incoming_info_csta(sip, msg);
4769 return;
4772 from = parse_from(sipmsg_find_header(msg, "From"));
4773 session = sipe_session_find_chat_by_callid(sip, callid);
4774 if (!session) {
4775 session = sipe_session_find_im(sip, from);
4777 if (!session) {
4778 g_free(from);
4779 return;
4782 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4784 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4785 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4786 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4788 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4790 if (xn_request_rm) {
4791 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4792 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4793 gchar *body = g_strdup_printf(
4794 "<?xml version=\"1.0\"?>\r\n"
4795 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4796 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4797 sip->username,
4798 session->bid < bid ? "true" : "false");
4799 send_sip_response(sip->gc, msg, 200, "OK", body);
4800 g_free(body);
4801 } else if (xn_set_rm) {
4802 gchar *body;
4803 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4804 g_free(session->roster_manager);
4805 session->roster_manager = g_strdup(rm);
4807 body = g_strdup_printf(
4808 "<?xml version=\"1.0\"?>\r\n"
4809 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4810 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4811 sip->username);
4812 send_sip_response(sip->gc, msg, 200, "OK", body);
4813 g_free(body);
4815 xmlnode_free(xn_action);
4818 else
4820 /* looks like purple lacks typing notification for chat */
4821 if (!session->is_multiparty && !session->focus_uri) {
4822 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4823 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4824 "status");
4825 if (sipe_strequal(status, "type")) {
4826 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4827 } else if (sipe_strequal(status, "idle")) {
4828 serv_got_typing_stopped(sip->gc, from);
4830 xmlnode_free(xn_keyboard_activity);
4833 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4835 g_free(from);
4838 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4840 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4841 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4842 struct sip_session *session;
4843 struct sip_dialog *dialog;
4845 /* collect dialog identification
4846 * we need callid, ourtag and theirtag to unambiguously identify dialog
4848 /* take data before 'msg' will be modified by send_sip_response */
4849 dialog = g_new0(struct sip_dialog, 1);
4850 dialog->callid = g_strdup(callid);
4851 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4852 dialog->with = g_strdup(from);
4853 sipe_dialog_parse(dialog, msg, FALSE);
4855 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4857 session = sipe_session_find_chat_by_callid(sip, callid);
4858 if (!session) {
4859 session = sipe_session_find_im(sip, from);
4861 if (!session) {
4862 sipe_dialog_free(dialog);
4863 g_free(from);
4864 return;
4867 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4868 g_free(session->roster_manager);
4869 session->roster_manager = NULL;
4872 /* This what BYE is essentially for - terminating dialog */
4873 sipe_dialog_remove_3(session, dialog);
4874 sipe_dialog_free(dialog);
4875 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4876 sipe_conf_immcu_closed(sip, session);
4877 } else if (session->is_multiparty) {
4878 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4881 g_free(from);
4884 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4886 gchar *self = sip_uri_self(sip);
4887 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4888 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4889 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4890 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4891 struct sip_session *session;
4892 struct sip_dialog *dialog;
4894 session = sipe_session_find_chat_by_callid(sip, callid);
4895 dialog = sipe_dialog_find(session, from);
4897 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
4898 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4899 } else {
4900 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4902 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4905 g_free(self);
4906 g_free(from);
4907 g_free(refer_to);
4908 g_free(referred_by);
4911 static unsigned int
4912 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4914 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4915 struct sip_session *session;
4916 struct sip_dialog *dialog;
4918 if (state == PURPLE_NOT_TYPING)
4919 return 0;
4921 session = sipe_session_find_im(sip, who);
4922 dialog = sipe_dialog_find(session, who);
4924 if (session && dialog && dialog->is_established) {
4925 send_sip_request(gc, "INFO", who, who,
4926 "Content-Type: application/xml\r\n",
4927 SIPE_SEND_TYPING, dialog, NULL);
4929 return SIPE_TYPING_SEND_TIMEOUT;
4932 static gboolean resend_timeout(struct sipe_account_data *sip)
4934 GSList *tmp = sip->transactions;
4935 time_t currtime = time(NULL);
4936 while (tmp) {
4937 struct transaction *trans = tmp->data;
4938 tmp = tmp->next;
4939 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4940 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4941 /* TODO 408 */
4942 } else {
4943 if ((currtime - trans->time > 2) && trans->retries == 0) {
4944 trans->retries++;
4945 sendout_sipmsg(sip, trans->msg);
4949 return TRUE;
4952 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4953 SIPE_UNUSED_PARAMETER void *unused)
4955 /* register again when security token expires */
4956 /* we have to start a new authentication as the security token
4957 * is almost expired by sending a not signed REGISTER message */
4958 purple_debug_info("sipe", "do a full reauthentication\n");
4959 sipe_auth_free(&sip->registrar);
4960 sipe_auth_free(&sip->proxy);
4961 sip->registerstatus = 0;
4962 do_register(sip);
4963 sip->reauthenticate_set = FALSE;
4966 static gboolean
4967 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4968 struct sipmsg *msg,
4969 GSList *parsed_body)
4971 gboolean found = FALSE;
4973 if (parsed_body) {
4974 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4976 if (sipe_strequal(invitation_command, "INVITE")) {
4977 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4978 found = TRUE;
4979 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4980 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4981 found = TRUE;
4982 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4983 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4984 found = TRUE;
4987 return found;
4990 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4992 gchar *from;
4993 const gchar *contenttype;
4994 gboolean found = FALSE;
4996 from = parse_from(sipmsg_find_header(msg, "From"));
4998 if (!from) return;
5000 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
5002 contenttype = sipmsg_find_header(msg, "Content-Type");
5003 if (g_str_has_prefix(contenttype, "text/plain")
5004 || g_str_has_prefix(contenttype, "text/html")
5005 || g_str_has_prefix(contenttype, "multipart/related")
5006 || g_str_has_prefix(contenttype, "multipart/alternative"))
5008 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5009 gchar *html = get_html_message(contenttype, msg->body);
5011 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5012 if (!session) {
5013 session = sipe_session_find_im(sip, from);
5016 if (session && session->focus_uri) { /* a conference */
5017 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5018 gchar *sender = parse_from(tmp);
5019 g_free(tmp);
5020 serv_got_chat_in(sip->gc, session->chat_id, sender,
5021 PURPLE_MESSAGE_RECV, html, time(NULL));
5022 g_free(sender);
5023 } else if (session && session->is_multiparty) { /* a multiparty chat */
5024 serv_got_chat_in(sip->gc, session->chat_id, from,
5025 PURPLE_MESSAGE_RECV, html, time(NULL));
5026 } else {
5027 serv_got_im(sip->gc, from, html, 0, time(NULL));
5029 g_free(html);
5030 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5031 found = TRUE;
5033 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5034 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5035 xmlnode *state;
5036 gchar *statedata;
5038 if (!isc) {
5039 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
5040 g_free(from);
5041 return;
5044 state = xmlnode_get_child(isc, "state");
5046 if (!state) {
5047 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5048 xmlnode_free(isc);
5049 g_free(from);
5050 return;
5053 statedata = xmlnode_get_data(state);
5054 if (statedata) {
5055 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5056 else serv_got_typing_stopped(sip->gc, from);
5058 g_free(statedata);
5060 xmlnode_free(isc);
5061 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5062 found = TRUE;
5063 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5064 GSList *body = sipe_ft_parse_msg_body(msg->body);
5065 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5066 sipe_utils_nameval_free(body);
5067 if (found) {
5068 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5071 if (!found) {
5072 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5073 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5074 if (!session) {
5075 session = sipe_session_find_im(sip, from);
5077 if (session) {
5078 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5079 from);
5080 sipe_present_err(sip, session, errmsg);
5081 g_free(errmsg);
5084 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5085 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5087 g_free(from);
5090 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5092 gchar *body;
5093 gchar *newTag;
5094 const gchar *oldHeader;
5095 gchar *newHeader;
5096 gboolean is_multiparty = FALSE;
5097 gboolean is_triggered = FALSE;
5098 gboolean was_multiparty = TRUE;
5099 gboolean just_joined = FALSE;
5100 gchar *from;
5101 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5102 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5103 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5104 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5105 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5106 GSList *end_points = NULL;
5107 char *tmp = NULL;
5108 struct sip_session *session;
5109 const gchar *ms_text_format;
5111 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5112 g_free(tmp);
5114 /* Invitation to join conference */
5115 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5116 process_incoming_invite_conf(sip, msg);
5117 return;
5120 /* Only accept text invitations */
5121 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5122 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5123 return;
5126 // TODO There *must* be a better way to clean up the To header to add a tag...
5127 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5128 oldHeader = sipmsg_find_header(msg, "To");
5129 newTag = gentag();
5130 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5131 sipmsg_remove_header_now(msg, "To");
5132 sipmsg_add_header_now(msg, "To", newHeader);
5133 g_free(newHeader);
5135 if (end_points_hdr) {
5136 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5138 if (g_slist_length(end_points) > 2) {
5139 is_multiparty = TRUE;
5142 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5143 is_triggered = TRUE;
5144 is_multiparty = TRUE;
5147 session = sipe_session_find_chat_by_callid(sip, callid);
5148 /* Convert to multiparty */
5149 if (session && is_multiparty && !session->is_multiparty) {
5150 g_free(session->with);
5151 session->with = NULL;
5152 was_multiparty = FALSE;
5153 session->is_multiparty = TRUE;
5154 session->chat_id = rand();
5157 if (!session && is_multiparty) {
5158 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5160 /* IM session */
5161 from = parse_from(sipmsg_find_header(msg, "From"));
5162 if (!session) {
5163 session = sipe_session_find_or_add_im(sip, from);
5166 if (session) {
5167 g_free(session->callid);
5168 session->callid = g_strdup(callid);
5170 session->is_multiparty = is_multiparty;
5171 if (roster_manager) {
5172 session->roster_manager = g_strdup(roster_manager);
5176 if (is_multiparty && end_points) {
5177 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5178 GSList *entry = end_points;
5179 while (entry) {
5180 struct sip_dialog *dialog;
5181 struct sipendpoint *end_point = entry->data;
5182 entry = entry->next;
5184 if (!g_strcasecmp(from, end_point->contact) ||
5185 !g_strcasecmp(to, end_point->contact))
5186 continue;
5188 dialog = sipe_dialog_find(session, end_point->contact);
5189 if (dialog) {
5190 g_free(dialog->theirepid);
5191 dialog->theirepid = end_point->epid;
5192 end_point->epid = NULL;
5193 } else {
5194 dialog = sipe_dialog_add(session);
5196 dialog->callid = g_strdup(session->callid);
5197 dialog->with = end_point->contact;
5198 end_point->contact = NULL;
5199 dialog->theirepid = end_point->epid;
5200 end_point->epid = NULL;
5202 just_joined = TRUE;
5204 /* send triggered INVITE */
5205 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5208 g_free(to);
5211 if (end_points) {
5212 GSList *entry = end_points;
5213 while (entry) {
5214 struct sipendpoint *end_point = entry->data;
5215 entry = entry->next;
5216 g_free(end_point->contact);
5217 g_free(end_point->epid);
5218 g_free(end_point);
5220 g_slist_free(end_points);
5223 if (session) {
5224 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5225 if (dialog) {
5226 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5227 sipe_dialog_parse_routes(dialog, msg, FALSE);
5228 } else {
5229 dialog = sipe_dialog_add(session);
5231 dialog->callid = g_strdup(session->callid);
5232 dialog->with = g_strdup(from);
5233 sipe_dialog_parse(dialog, msg, FALSE);
5235 if (!dialog->ourtag) {
5236 dialog->ourtag = newTag;
5237 newTag = NULL;
5240 just_joined = TRUE;
5242 } else {
5243 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5245 g_free(newTag);
5247 if (is_multiparty && !session->conv) {
5248 gchar *chat_title = sipe_chat_get_name(callid);
5249 gchar *self = sip_uri_self(sip);
5250 /* create prpl chat */
5251 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5252 session->chat_title = g_strdup(chat_title);
5253 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5254 /* add self */
5255 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5256 self, NULL,
5257 PURPLE_CBFLAGS_NONE, FALSE);
5258 g_free(chat_title);
5259 g_free(self);
5262 if (is_multiparty && !was_multiparty) {
5263 /* add current IM counterparty to chat */
5264 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5265 sipe_dialog_first(session)->with, NULL,
5266 PURPLE_CBFLAGS_NONE, FALSE);
5269 /* add inviting party to chat */
5270 if (just_joined && session->conv) {
5271 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5272 from, NULL,
5273 PURPLE_CBFLAGS_NONE, TRUE);
5276 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5278 /* This used only in 2005 official client, not 2007 or Reuters.
5279 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5280 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5282 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5283 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5284 if (is_multiparty ||
5285 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5287 if (ms_text_format) {
5288 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5290 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5291 if (tmp) {
5292 gsize len;
5293 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5295 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5297 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5298 sipe_utils_nameval_free(parsed_body);
5299 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5301 g_free(tmp);
5303 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5305 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5306 gchar *html = get_html_message(ms_text_format, NULL);
5307 if (html) {
5308 if (is_multiparty) {
5309 serv_got_chat_in(sip->gc, session->chat_id, from,
5310 PURPLE_MESSAGE_RECV, html, time(NULL));
5311 } else {
5312 serv_got_im(sip->gc, from, html, 0, time(NULL));
5314 g_free(html);
5315 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5321 g_free(from);
5323 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5324 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5325 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5327 body = g_strdup_printf(
5328 "v=0\r\n"
5329 "o=- 0 0 IN IP4 %s\r\n"
5330 "s=session\r\n"
5331 "c=IN IP4 %s\r\n"
5332 "t=0 0\r\n"
5333 "m=%s %d sip sip:%s\r\n"
5334 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5335 purple_network_get_my_ip(-1),
5336 purple_network_get_my_ip(-1),
5337 sip->ocs2007 ? "message" : "x-ms-message",
5338 sip->realport,
5339 sip->username);
5340 send_sip_response(sip->gc, msg, 200, "OK", body);
5341 g_free(body);
5344 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5346 gchar *body;
5348 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5349 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5350 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5352 body = g_strdup_printf(
5353 "v=0\r\n"
5354 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5355 "s=session\r\n"
5356 "c=IN IP4 0.0.0.0\r\n"
5357 "t=0 0\r\n"
5358 "m=%s %d sip sip:%s\r\n"
5359 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5360 sip->ocs2007 ? "message" : "x-ms-message",
5361 sip->realport,
5362 sip->username);
5363 send_sip_response(sip->gc, msg, 200, "OK", body);
5364 g_free(body);
5367 static const char*
5368 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5370 const char *res = "NTLM";
5371 #ifdef HAVE_KERBEROS
5372 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5373 res = "Kerberos";
5375 #else
5376 (void) sip; /* make compiler happy */
5377 #endif
5378 return res;
5381 static void sipe_connection_cleanup(struct sipe_account_data *);
5382 static void create_connection(struct sipe_account_data *, gchar *, int);
5384 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5385 SIPE_UNUSED_PARAMETER struct transaction *trans)
5387 gchar *tmp;
5388 const gchar *expires_header;
5389 int expires, i;
5390 GSList *hdr = msg->headers;
5391 struct sipnameval *elem;
5393 expires_header = sipmsg_find_header(msg, "Expires");
5394 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5395 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5397 switch (msg->response) {
5398 case 200:
5399 if (expires == 0) {
5400 sip->registerstatus = 0;
5401 } else {
5402 const gchar *contact_hdr;
5403 gchar *gruu = NULL;
5404 gchar *epid;
5405 gchar *uuid;
5406 gchar *timeout;
5407 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5408 const char *auth_scheme;
5410 if (!sip->reregister_set) {
5411 gchar *action_name = g_strdup_printf("<%s>", "registration");
5412 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5413 g_free(action_name);
5414 sip->reregister_set = TRUE;
5417 sip->registerstatus = 3;
5419 if (server_hdr && !sip->server_version) {
5420 sip->server_version = g_strdup(server_hdr);
5421 g_free(default_ua);
5422 default_ua = NULL;
5425 auth_scheme = sipe_get_auth_scheme_name(sip);
5426 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5428 if (tmp) {
5429 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5430 fill_auth(tmp, &sip->registrar);
5433 if (!sip->reauthenticate_set) {
5434 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5435 guint reauth_timeout;
5436 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5437 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5438 reauth_timeout = sip->registrar.expires - 300;
5439 } else {
5440 /* NTLM: we have to reauthenticate as our security token expires
5441 after eight hours (be five minutes early) */
5442 reauth_timeout = (8 * 3600) - 300;
5444 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5445 g_free(action_name);
5446 sip->reauthenticate_set = TRUE;
5449 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5451 epid = get_epid(sip);
5452 uuid = generateUUIDfromEPID(epid);
5453 g_free(epid);
5455 // There can be multiple Contact headers (one per location where the user is logged in) so
5456 // make sure to only get the one for this uuid
5457 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5458 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5459 if (valid_contact) {
5460 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5461 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5462 g_free(valid_contact);
5463 break;
5464 } else {
5465 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5468 g_free(uuid);
5470 g_free(sip->contact);
5471 if(gruu) {
5472 sip->contact = g_strdup_printf("<%s>", gruu);
5473 g_free(gruu);
5474 } else {
5475 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5476 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);
5478 sip->ocs2007 = FALSE;
5479 sip->batched_support = FALSE;
5481 while(hdr)
5483 elem = hdr->data;
5484 if (sipe_strcase_equal(elem->name, "Supported")) {
5485 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5486 /* We interpret this as OCS2007+ indicator */
5487 sip->ocs2007 = TRUE;
5488 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5490 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5491 sip->batched_support = TRUE;
5492 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5495 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5496 gchar **caps = g_strsplit(elem->value,",",0);
5497 i = 0;
5498 while (caps[i]) {
5499 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5500 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5501 i++;
5503 g_strfreev(caps);
5505 hdr = g_slist_next(hdr);
5508 /* rejoin open chats to be able to use them by continue to send messages */
5509 purple_conversation_foreach(sipe_rejoin_chat);
5511 /* subscriptions */
5512 if (!sip->subscribed) { //do it just once, not every re-register
5514 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5515 (GCompareFunc)g_ascii_strcasecmp)) {
5516 sipe_subscribe_roaming_contacts(sip);
5519 /* For 2007+ it does not make sence to subscribe to:
5520 * vnd-microsoft-roaming-ACL
5521 * vnd-microsoft-provisioning (not v2)
5522 * presence.wpending
5523 * These are for backward compatibility.
5525 if (sip->ocs2007)
5527 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5528 (GCompareFunc)g_ascii_strcasecmp)) {
5529 sipe_subscribe_roaming_self(sip);
5531 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5532 (GCompareFunc)g_ascii_strcasecmp)) {
5533 sipe_subscribe_roaming_provisioning_v2(sip);
5536 /* For 2005- servers */
5537 else
5539 //sipe_options_request(sip, sip->sipdomain);
5541 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5542 (GCompareFunc)g_ascii_strcasecmp)) {
5543 sipe_subscribe_roaming_acl(sip);
5545 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5546 (GCompareFunc)g_ascii_strcasecmp)) {
5547 sipe_subscribe_roaming_provisioning(sip);
5549 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5550 (GCompareFunc)g_ascii_strcasecmp)) {
5551 sipe_subscribe_presence_wpending(sip, msg);
5554 /* For 2007+ we publish our initial statuses and calendar data only after
5555 * received our existing publications in sipe_process_roaming_self()
5556 * Only in this case we know versions of current publications made
5557 * on our behalf.
5559 /* For 2005- we publish our initial statuses only after
5560 * received our existing UserInfo data in response to
5561 * self subscription.
5562 * Only in this case we won't override existing UserInfo data
5563 * set earlier or by other client on our behalf.
5567 sip->subscribed = TRUE;
5570 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5571 "timeout=", ";", NULL);
5572 if (timeout != NULL) {
5573 sscanf(timeout, "%u", &sip->keepalive_timeout);
5574 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5575 sip->keepalive_timeout);
5576 g_free(timeout);
5579 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5581 break;
5582 case 301:
5584 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5586 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5587 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5588 gchar **tmp;
5589 gchar *hostname;
5590 int port = 0;
5591 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5592 int i = 1;
5594 tmp = g_strsplit(parts[0], ":", 0);
5595 hostname = g_strdup(tmp[0]);
5596 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5597 g_strfreev(tmp);
5599 while (parts[i]) {
5600 tmp = g_strsplit(parts[i], "=", 0);
5601 if (tmp[1]) {
5602 if (g_strcasecmp("transport", tmp[0]) == 0) {
5603 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5604 transport = SIPE_TRANSPORT_TCP;
5605 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5606 transport = SIPE_TRANSPORT_UDP;
5610 g_strfreev(tmp);
5611 i++;
5613 g_strfreev(parts);
5615 /* Close old connection */
5616 sipe_connection_cleanup(sip);
5618 /* Create new connection */
5619 sip->transport = transport;
5620 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5621 hostname, port, TRANSPORT_DESCRIPTOR);
5622 create_connection(sip, hostname, port);
5624 g_free(redirect);
5626 break;
5627 case 401:
5628 if (sip->registerstatus != 2) {
5629 const char *auth_scheme;
5630 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5631 if (sip->registrar.retries > 3) {
5632 sip->gc->wants_to_die = TRUE;
5633 purple_connection_error(sip->gc, _("Authentication failed"));
5634 return TRUE;
5637 auth_scheme = sipe_get_auth_scheme_name(sip);
5638 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5640 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5641 if (!tmp) {
5642 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5643 sip->gc->wants_to_die = TRUE;
5644 purple_connection_error(sip->gc, tmp2);
5645 g_free(tmp2);
5646 return TRUE;
5648 fill_auth(tmp, &sip->registrar);
5649 sip->registerstatus = 2;
5650 if (sip->account->disconnecting) {
5651 do_register_exp(sip, 0);
5652 } else {
5653 do_register(sip);
5656 break;
5657 case 403:
5659 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5660 gchar **reason = NULL;
5661 gchar *warning;
5662 if (diagnostics != NULL) {
5663 /* Example header:
5664 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5666 reason = g_strsplit(diagnostics, "\"", 0);
5668 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5669 (reason && reason[1]) ? reason[1] : _("no reason given"));
5670 g_strfreev(reason);
5672 sip->gc->wants_to_die = TRUE;
5673 purple_connection_error(sip->gc, warning);
5674 g_free(warning);
5675 return TRUE;
5677 break;
5678 case 404:
5680 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5681 gchar *reason = NULL;
5682 gchar *warning;
5683 if (diagnostics != NULL) {
5684 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5686 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5687 diagnostics ? (reason ? reason : _("no reason given")) :
5688 _("SIP is either not enabled for the destination URI or it does not exist"));
5689 g_free(reason);
5691 sip->gc->wants_to_die = TRUE;
5692 purple_connection_error(sip->gc, warning);
5693 g_free(warning);
5694 return TRUE;
5696 break;
5697 case 503:
5698 case 504: /* Server time-out */
5700 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5701 gchar *reason = NULL;
5702 gchar *warning;
5703 if (diagnostics != NULL) {
5704 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5706 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5707 g_free(reason);
5709 sip->gc->wants_to_die = TRUE;
5710 purple_connection_error(sip->gc, warning);
5711 g_free(warning);
5712 return TRUE;
5714 break;
5716 return TRUE;
5720 * Returns 2005-style activity and Availability.
5722 * @param status Sipe statis id.
5724 static void
5725 sipe_get_act_avail_by_status_2005(const char *status,
5726 int *activity,
5727 int *availability)
5729 int avail = 300; /* online */
5730 int act = 400; /* Available */
5732 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5733 act = 100;
5734 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5735 // act = 150;
5736 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5737 act = 300;
5738 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5739 act = 400;
5740 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5741 // act = 500;
5742 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5743 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5744 act = 600;
5745 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5746 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5747 avail = 0; /* offline */
5748 act = 100;
5749 } else {
5750 act = 400; /* Available */
5753 if (activity) *activity = act;
5754 if (availability) *availability = avail;
5758 * [MS-SIP] 2.2.1
5760 * @param activity 2005 aggregated activity. Ex.: 600
5761 * @param availablity 2005 aggregated availablity. Ex.: 300
5763 static const char *
5764 sipe_get_status_by_act_avail_2005(const int activity,
5765 const int availablity,
5766 char **activity_desc)
5768 const char *status_id = NULL;
5769 const char *act = NULL;
5771 if (activity < 150) {
5772 status_id = SIPE_STATUS_ID_AWAY;
5773 } else if (activity < 200) {
5774 //status_id = SIPE_STATUS_ID_LUNCH;
5775 status_id = SIPE_STATUS_ID_AWAY;
5776 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5777 } else if (activity < 300) {
5778 //status_id = SIPE_STATUS_ID_IDLE;
5779 status_id = SIPE_STATUS_ID_AWAY;
5780 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5781 } else if (activity < 400) {
5782 status_id = SIPE_STATUS_ID_BRB;
5783 } else if (activity < 500) {
5784 status_id = SIPE_STATUS_ID_AVAILABLE;
5785 } else if (activity < 600) {
5786 //status_id = SIPE_STATUS_ID_ON_PHONE;
5787 status_id = SIPE_STATUS_ID_BUSY;
5788 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5789 } else if (activity < 700) {
5790 status_id = SIPE_STATUS_ID_BUSY;
5791 } else if (activity < 800) {
5792 status_id = SIPE_STATUS_ID_AWAY;
5793 } else {
5794 status_id = SIPE_STATUS_ID_AVAILABLE;
5797 if (availablity < 100)
5798 status_id = SIPE_STATUS_ID_OFFLINE;
5800 if (activity_desc && act) {
5801 g_free(*activity_desc);
5802 *activity_desc = g_strdup(act);
5805 return status_id;
5809 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5811 static const char*
5812 sipe_get_status_by_availability(int avail,
5813 char** activity_desc)
5815 const char *status;
5816 const char *act = NULL;
5818 if (avail < 3000) {
5819 status = SIPE_STATUS_ID_OFFLINE;
5820 } else if (avail < 4500) {
5821 status = SIPE_STATUS_ID_AVAILABLE;
5822 } else if (avail < 6000) {
5823 //status = SIPE_STATUS_ID_IDLE;
5824 status = SIPE_STATUS_ID_AVAILABLE;
5825 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5826 } else if (avail < 7500) {
5827 status = SIPE_STATUS_ID_BUSY;
5828 } else if (avail < 9000) {
5829 //status = SIPE_STATUS_ID_BUSYIDLE;
5830 status = SIPE_STATUS_ID_BUSY;
5831 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5832 } else if (avail < 12000) {
5833 status = SIPE_STATUS_ID_DND;
5834 } else if (avail < 15000) {
5835 status = SIPE_STATUS_ID_BRB;
5836 } else if (avail < 18000) {
5837 status = SIPE_STATUS_ID_AWAY;
5838 } else {
5839 status = SIPE_STATUS_ID_OFFLINE;
5842 if (activity_desc && act) {
5843 g_free(*activity_desc);
5844 *activity_desc = g_strdup(act);
5847 return status;
5851 * Returns 2007-style availability value
5853 * @param sipe_status_id (in)
5854 * @param activity_token (out) Must be g_free()'d after use if consumed.
5856 static int
5857 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5859 int availability;
5860 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5862 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5863 availability = 15500;
5864 if (!activity_token || !(*activity_token)) {
5865 activity = SIPE_ACTIVITY_AWAY;
5867 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5868 availability = 12500;
5869 activity = SIPE_ACTIVITY_BRB;
5870 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5871 availability = 9500;
5872 activity = SIPE_ACTIVITY_DND;
5873 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5874 availability = 6500;
5875 if (!activity_token || !(*activity_token)) {
5876 activity = SIPE_ACTIVITY_BUSY;
5878 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5879 availability = 3500;
5880 activity = SIPE_ACTIVITY_ONLINE;
5881 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5882 availability = 0;
5883 } else {
5884 // Offline or invisible
5885 availability = 18500;
5886 activity = SIPE_ACTIVITY_OFFLINE;
5889 if (activity_token) {
5890 *activity_token = g_strdup(sipe_activity_map[activity].token);
5892 return availability;
5895 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5897 const char *uri;
5898 sipe_xml *xn_categories;
5899 const sipe_xml *xn_category;
5900 const char *status = NULL;
5901 gboolean do_update_status = FALSE;
5902 gboolean has_note_cleaned = FALSE;
5903 gboolean has_free_busy_cleaned = FALSE;
5905 xn_categories = sipe_xml_parse(data, len);
5906 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
5908 for (xn_category = sipe_xml_child(xn_categories, "category");
5909 xn_category ;
5910 xn_category = sipe_xml_twin(xn_category) )
5912 const sipe_xml *xn_node;
5913 const char *tmp;
5914 const char *attrVar = sipe_xml_attribute(xn_category, "name");
5915 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
5916 sipe_utils_str_to_time(tmp) : 0;
5918 /* contactCard */
5919 if (sipe_strequal(attrVar, "contactCard"))
5921 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
5923 if (card) {
5924 const sipe_xml *node;
5925 /* identity - Display Name and email */
5926 node = sipe_xml_child(card, "identity");
5927 if (node) {
5928 char* display_name = sipe_xml_data(
5929 sipe_xml_child(node, "name/displayName"));
5930 char* email = sipe_xml_data(
5931 sipe_xml_child(node, "email"));
5933 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5934 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5936 g_free(display_name);
5937 g_free(email);
5939 /* company */
5940 node = sipe_xml_child(card, "company");
5941 if (node) {
5942 char* company = sipe_xml_data(node);
5943 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5944 g_free(company);
5946 /* department */
5947 node = sipe_xml_child(card, "department");
5948 if (node) {
5949 char* department = sipe_xml_data(node);
5950 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5951 g_free(department);
5953 /* title */
5954 node = sipe_xml_child(card, "title");
5955 if (node) {
5956 char* title = sipe_xml_data(node);
5957 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5958 g_free(title);
5960 /* office */
5961 node = sipe_xml_child(card, "office");
5962 if (node) {
5963 char* office = sipe_xml_data(node);
5964 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5965 g_free(office);
5967 /* site (url) */
5968 node = sipe_xml_child(card, "url");
5969 if (node) {
5970 char* site = sipe_xml_data(node);
5971 sipe_update_user_info(sip, uri, SITE_PROP, site);
5972 g_free(site);
5974 /* phone */
5975 for (node = sipe_xml_child(card, "phone");
5976 node;
5977 node = sipe_xml_twin(node))
5979 const char *phone_type = sipe_xml_attribute(node, "type");
5980 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
5981 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
5983 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5985 g_free(phone);
5986 g_free(phone_display_string);
5988 /* address */
5989 for (node = sipe_xml_child(card, "address");
5990 node;
5991 node = sipe_xml_twin(node))
5993 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
5994 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
5995 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
5996 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
5997 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
5998 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6000 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6001 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6002 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6003 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6004 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6006 g_free(street);
6007 g_free(city);
6008 g_free(state);
6009 g_free(zipcode);
6010 g_free(country_code);
6012 break;
6017 /* note */
6018 else if (sipe_strequal(attrVar, "note"))
6020 if (uri) {
6021 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6023 if (!has_note_cleaned) {
6024 has_note_cleaned = TRUE;
6026 g_free(sbuddy->note);
6027 sbuddy->note = NULL;
6028 sbuddy->is_oof_note = FALSE;
6029 sbuddy->note_since = publish_time;
6031 do_update_status = TRUE;
6033 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6034 /* clean up in case no 'note' element is supplied
6035 * which indicate note removal in client
6037 g_free(sbuddy->note);
6038 sbuddy->note = NULL;
6039 sbuddy->is_oof_note = FALSE;
6040 sbuddy->note_since = publish_time;
6042 xn_node = sipe_xml_child(xn_category, "note/body");
6043 if (xn_node) {
6044 char *tmp;
6045 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6046 g_free(tmp);
6047 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6048 sbuddy->note_since = publish_time;
6050 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6051 uri, sbuddy->note ? sbuddy->note : "");
6053 /* to trigger UI refresh in case no status info is supplied in this update */
6054 do_update_status = TRUE;
6058 /* state */
6059 else if(sipe_strequal(attrVar, "state"))
6061 char *tmp;
6062 int availability;
6063 const sipe_xml *xn_availability;
6064 const sipe_xml *xn_activity;
6065 const sipe_xml *xn_meeting_subject;
6066 const sipe_xml *xn_meeting_location;
6067 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6069 xn_node = sipe_xml_child(xn_category, "state");
6070 if (!xn_node) continue;
6071 xn_availability = sipe_xml_child(xn_node, "availability");
6072 if (!xn_availability) continue;
6073 xn_activity = sipe_xml_child(xn_node, "activity");
6074 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6075 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6077 tmp = sipe_xml_data(xn_availability);
6078 availability = atoi(tmp);
6079 g_free(tmp);
6081 /* activity, meeting_subject, meeting_location */
6082 if (sbuddy) {
6083 char *tmp = NULL;
6085 /* activity */
6086 g_free(sbuddy->activity);
6087 sbuddy->activity = NULL;
6088 if (xn_activity) {
6089 const char *token = sipe_xml_attribute(xn_activity, "token");
6090 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6092 /* from token */
6093 if (!is_empty(token)) {
6094 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6096 /* from custom element */
6097 if (xn_custom) {
6098 char *custom = sipe_xml_data(xn_custom);
6100 if (!is_empty(custom)) {
6101 sbuddy->activity = custom;
6102 custom = NULL;
6104 g_free(custom);
6107 /* meeting_subject */
6108 g_free(sbuddy->meeting_subject);
6109 sbuddy->meeting_subject = NULL;
6110 if (xn_meeting_subject) {
6111 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6113 if (!is_empty(meeting_subject)) {
6114 sbuddy->meeting_subject = meeting_subject;
6115 meeting_subject = NULL;
6117 g_free(meeting_subject);
6119 /* meeting_location */
6120 g_free(sbuddy->meeting_location);
6121 sbuddy->meeting_location = NULL;
6122 if (xn_meeting_location) {
6123 char *meeting_location = sipe_xml_data(xn_meeting_location);
6125 if (!is_empty(meeting_location)) {
6126 sbuddy->meeting_location = meeting_location;
6127 meeting_location = NULL;
6129 g_free(meeting_location);
6132 status = sipe_get_status_by_availability(availability, &tmp);
6133 if (sbuddy->activity && tmp) {
6134 char *tmp2 = sbuddy->activity;
6136 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6137 g_free(tmp);
6138 g_free(tmp2);
6139 } else if (tmp) {
6140 sbuddy->activity = tmp;
6144 do_update_status = TRUE;
6146 /* calendarData */
6147 else if(sipe_strequal(attrVar, "calendarData"))
6149 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6150 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6151 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6153 if (sbuddy && xn_free_busy) {
6154 if (!has_free_busy_cleaned) {
6155 has_free_busy_cleaned = TRUE;
6157 g_free(sbuddy->cal_start_time);
6158 sbuddy->cal_start_time = NULL;
6160 g_free(sbuddy->cal_free_busy_base64);
6161 sbuddy->cal_free_busy_base64 = NULL;
6163 g_free(sbuddy->cal_free_busy);
6164 sbuddy->cal_free_busy = NULL;
6166 sbuddy->cal_free_busy_published = publish_time;
6169 if (publish_time >= sbuddy->cal_free_busy_published) {
6170 g_free(sbuddy->cal_start_time);
6171 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6173 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6174 15 : 0;
6176 g_free(sbuddy->cal_free_busy_base64);
6177 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6179 g_free(sbuddy->cal_free_busy);
6180 sbuddy->cal_free_busy = NULL;
6182 sbuddy->cal_free_busy_published = publish_time;
6184 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);
6188 if (sbuddy && xn_working_hours) {
6189 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6194 if (do_update_status) {
6195 if (!status) { /* no status category in this update, using contact's current status */
6196 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6197 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6198 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6199 status = purple_status_get_id(pstatus);
6202 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6203 sipe_got_user_status(sip, uri, status);
6206 sipe_xml_free(xn_categories);
6209 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6211 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6212 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6213 payload->host = g_strdup(host);
6214 payload->buddies = server;
6215 sipe_subscribe_presence_batched_routed(sip, payload);
6216 sipe_subscribe_presence_batched_routed_free(payload);
6219 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6221 xmlnode *xn_list;
6222 xmlnode *xn_resource;
6223 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6224 g_free, NULL);
6225 GSList *server;
6226 gchar *host;
6228 xn_list = xmlnode_from_str(data, len);
6230 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6231 xn_resource;
6232 xn_resource = xmlnode_get_next_twin(xn_resource) )
6234 const char *uri, *state;
6235 xmlnode *xn_instance;
6237 xn_instance = xmlnode_get_child(xn_resource, "instance");
6238 if (!xn_instance) continue;
6240 uri = xmlnode_get_attrib(xn_resource, "uri");
6241 state = xmlnode_get_attrib(xn_instance, "state");
6242 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6244 if (strstr(state, "resubscribe")) {
6245 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6247 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6248 gchar *user = g_strdup(uri);
6249 host = g_strdup(poolFqdn);
6250 server = g_hash_table_lookup(servers, host);
6251 server = g_slist_append(server, user);
6252 g_hash_table_insert(servers, host, server);
6253 } else {
6254 sipe_subscribe_presence_single(sip, (void *) uri);
6259 /* Send out any deferred poolFqdn subscriptions */
6260 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6261 g_hash_table_destroy(servers);
6263 xmlnode_free(xn_list);
6266 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6268 gchar *uri;
6269 gchar *getbasic;
6270 gchar *activity = NULL;
6271 xmlnode *pidf;
6272 xmlnode *basicstatus = NULL, *tuple, *status;
6273 gboolean isonline = FALSE;
6274 xmlnode *display_name_node;
6276 pidf = xmlnode_from_str(data, len);
6277 if (!pidf) {
6278 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6279 return;
6282 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6284 if ((status = xmlnode_get_child(tuple, "status"))) {
6285 basicstatus = xmlnode_get_child(status, "basic");
6289 if (!basicstatus) {
6290 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6291 xmlnode_free(pidf);
6292 return;
6295 getbasic = xmlnode_get_data(basicstatus);
6296 if (!getbasic) {
6297 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6298 xmlnode_free(pidf);
6299 return;
6302 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6303 if (strstr(getbasic, "open")) {
6304 isonline = TRUE;
6306 g_free(getbasic);
6308 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6310 display_name_node = xmlnode_get_child(pidf, "display-name");
6311 if (display_name_node) {
6312 char * display_name = xmlnode_get_data(display_name_node);
6314 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6315 g_free(display_name);
6318 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6319 if ((status = xmlnode_get_child(tuple, "status"))) {
6320 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6321 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6322 activity = xmlnode_get_data(basicstatus);
6323 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6329 if (isonline) {
6330 const gchar * status_id = NULL;
6331 if (activity) {
6332 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6333 status_id = SIPE_STATUS_ID_BUSY;
6334 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6335 status_id = SIPE_STATUS_ID_AWAY;
6339 if (!status_id) {
6340 status_id = SIPE_STATUS_ID_AVAILABLE;
6343 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6344 sipe_got_user_status(sip, uri, status_id);
6345 } else {
6346 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6349 g_free(activity);
6350 g_free(uri);
6351 xmlnode_free(pidf);
6354 /** 2005 */
6355 static void
6356 sipe_user_info_has_updated(struct sipe_account_data *sip,
6357 xmlnode *xn_userinfo)
6359 xmlnode *xn_states;
6361 g_free(sip->user_states);
6362 sip->user_states = NULL;
6363 if ((xn_states = xmlnode_get_child(xn_userinfo, "states")) != NULL) {
6364 sip->user_states = xmlnode_to_str(xn_states, NULL);
6365 /* this is a hack-around to remove added newline after inner element,
6366 * state in this case, where it shouldn't be.
6367 * After several use of xmlnode_to_str, amount of added newlines
6368 * grows significantly.
6370 purple_str_strip_char(sip->user_states, '\n');
6371 //purple_str_strip_char(sip->user_states, '\r');
6374 /* Publish initial state if not yet.
6375 * Assuming this happens on initial responce to self subscription
6376 * so we've already updated our UserInfo.
6378 if (!sip->initial_state_published) {
6379 send_presence_soap(sip, FALSE);
6380 /* dalayed run */
6381 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6385 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6387 char *activity = NULL;
6388 const char *epid;
6389 const char *status_id = NULL;
6390 const char *name;
6391 char *uri;
6392 char *self_uri = sip_uri_self(sip);
6393 int avl;
6394 int act;
6395 const char *device_name = NULL;
6396 const char *cal_start_time = NULL;
6397 const char *cal_granularity = NULL;
6398 char *cal_free_busy_base64 = NULL;
6399 struct sipe_buddy *sbuddy;
6400 xmlnode *node;
6401 xmlnode *xn_presentity;
6402 xmlnode *xn_availability;
6403 xmlnode *xn_activity;
6404 xmlnode *xn_display_name;
6405 xmlnode *xn_email;
6406 xmlnode *xn_phone_number;
6407 xmlnode *xn_userinfo;
6408 xmlnode *xn_note;
6409 xmlnode *xn_oof;
6410 xmlnode *xn_state;
6411 xmlnode *xn_contact;
6412 char *note;
6413 char *free_activity;
6414 int user_avail;
6415 const char *user_avail_nil;
6416 int res_avail;
6417 time_t user_avail_since = 0;
6418 time_t activity_since = 0;
6420 /* fix for Reuters environment on Linux */
6421 if (data && strstr(data, "encoding=\"utf-16\"")) {
6422 char *tmp_data;
6423 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6424 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6425 g_free(tmp_data);
6426 } else {
6427 xn_presentity = xmlnode_from_str(data, len);
6430 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6431 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6432 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6433 xn_email = xmlnode_get_child(xn_presentity, "email");
6434 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6435 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6436 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6437 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6438 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6439 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6440 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6441 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6442 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6443 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6445 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6446 user_avail = 0;
6447 user_avail_since = 0;
6450 free_activity = NULL;
6452 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6453 uri = sip_uri_from_name(name);
6454 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6455 epid = xmlnode_get_attrib(xn_availability, "epid");
6456 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6458 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6459 res_avail = sipe_get_availability_by_status(status_id, NULL);
6460 if (user_avail > res_avail) {
6461 res_avail = user_avail;
6462 status_id = sipe_get_status_by_availability(user_avail, NULL);
6465 if (xn_display_name) {
6466 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6467 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6468 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6469 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6470 char *tel_uri = sip_to_tel_uri(phone_number);
6472 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6473 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6474 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6475 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6477 g_free(tel_uri);
6478 g_free(phone_label);
6479 g_free(phone_number);
6480 g_free(email);
6481 g_free(display_name);
6484 if (xn_contact) {
6485 /* tel */
6486 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6488 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6489 const char *phone_type = xmlnode_get_attrib(node, "type");
6490 char* phone = xmlnode_get_data(node);
6492 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6494 g_free(phone);
6498 /* devicePresence */
6499 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6500 xmlnode *xn_device_name;
6501 xmlnode *xn_calendar_info;
6502 xmlnode *xn_state;
6503 char *state;
6505 /* deviceName */
6506 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6507 xn_device_name = xmlnode_get_child(node, "deviceName");
6508 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6511 /* calendarInfo */
6512 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6513 if (xn_calendar_info) {
6514 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6516 if (cal_start_time) {
6517 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6518 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6520 if (cal_start_time_t_tmp > cal_start_time_t) {
6521 cal_start_time = cal_start_time_tmp;
6522 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6523 g_free(cal_free_busy_base64);
6524 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6526 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);
6528 } else {
6529 cal_start_time = cal_start_time_tmp;
6530 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6531 g_free(cal_free_busy_base64);
6532 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6534 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);
6538 /* state */
6539 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6540 if (xn_state) {
6541 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6542 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6544 state = xmlnode_get_data(xn_state);
6545 if (dev_avail_since > user_avail_since &&
6546 dev_avail >= res_avail)
6548 res_avail = dev_avail;
6549 if (!is_empty(state))
6551 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6552 g_free(activity);
6553 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6554 } else if (sipe_strequal(state, "presenting")) {
6555 g_free(activity);
6556 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6557 } else {
6558 activity = state;
6559 state = NULL;
6561 activity_since = dev_avail_since;
6563 status_id = sipe_get_status_by_availability(res_avail, &activity);
6565 g_free(state);
6569 /* oof */
6570 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6571 g_free(activity);
6572 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6573 activity_since = 0;
6576 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6577 if (sbuddy)
6579 g_free(sbuddy->activity);
6580 sbuddy->activity = activity;
6581 activity = NULL;
6583 sbuddy->activity_since = activity_since;
6585 sbuddy->user_avail = user_avail;
6586 sbuddy->user_avail_since = user_avail_since;
6588 g_free(sbuddy->note);
6589 sbuddy->note = NULL;
6590 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6592 sbuddy->is_oof_note = (xn_oof != NULL);
6594 g_free(sbuddy->device_name);
6595 sbuddy->device_name = NULL;
6596 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6598 if (!is_empty(cal_free_busy_base64)) {
6599 g_free(sbuddy->cal_start_time);
6600 sbuddy->cal_start_time = g_strdup(cal_start_time);
6602 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6604 g_free(sbuddy->cal_free_busy_base64);
6605 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6606 cal_free_busy_base64 = NULL;
6608 g_free(sbuddy->cal_free_busy);
6609 sbuddy->cal_free_busy = NULL;
6612 sbuddy->last_non_cal_status_id = status_id;
6613 g_free(sbuddy->last_non_cal_activity);
6614 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6616 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6617 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6619 sip->is_oof_note = sbuddy->is_oof_note;
6621 g_free(sip->note);
6622 sip->note = g_strdup(sbuddy->note);
6624 sip->note_since = time(NULL);
6627 g_free(sip->status);
6628 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6631 g_free(cal_free_busy_base64);
6632 g_free(activity);
6634 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6635 sipe_got_user_status(sip, uri, status_id);
6637 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6638 sipe_user_info_has_updated(sip, xn_userinfo);
6641 g_free(note);
6642 xmlnode_free(xn_presentity);
6643 g_free(uri);
6644 g_free(self_uri);
6647 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6649 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6651 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6653 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6654 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6656 const char *content = msg->body;
6657 unsigned length = msg->bodylen;
6658 PurpleMimeDocument *mime = NULL;
6660 if (strstr(ctype, "multipart"))
6662 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6663 const char *content_type;
6664 GList* parts;
6665 mime = purple_mime_document_parse(doc);
6666 parts = purple_mime_document_get_parts(mime);
6667 while(parts) {
6668 content = purple_mime_part_get_data(parts->data);
6669 length = purple_mime_part_get_length(parts->data);
6670 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6671 if(content_type && strstr(content_type,"application/rlmi+xml"))
6673 process_incoming_notify_rlmi_resub(sip, content, length);
6675 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6677 process_incoming_notify_msrtc(sip, content, length);
6679 else
6681 process_incoming_notify_rlmi(sip, content, length);
6683 parts = parts->next;
6685 g_free(doc);
6687 if (mime)
6689 purple_mime_document_free(mime);
6692 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6694 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6696 else if(strstr(ctype, "application/rlmi+xml"))
6698 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6701 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6703 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6705 else
6707 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6711 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6713 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6714 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6716 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6718 if (ctype &&
6719 strstr(ctype, "multipart") &&
6720 (strstr(ctype, "application/rlmi+xml") ||
6721 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6722 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6723 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6724 GList *parts = purple_mime_document_get_parts(mime);
6725 GSList *buddies = NULL;
6726 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6728 while (parts) {
6729 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6730 purple_mime_part_get_length(parts->data));
6732 if (xml && !sipe_strequal(xml->name, "list")) {
6733 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6735 buddies = g_slist_append(buddies, uri);
6737 xmlnode_free(xml);
6739 parts = parts->next;
6741 g_free(doc);
6742 if (mime) purple_mime_document_free(mime);
6744 payload->host = g_strdup(who);
6745 payload->buddies = buddies;
6746 sipe_schedule_action(action_name, timeout,
6747 sipe_subscribe_presence_batched_routed,
6748 sipe_subscribe_presence_batched_routed_free,
6749 sip, payload);
6750 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6752 } else {
6753 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6754 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
6756 g_free(action_name);
6760 * Dispatcher for all incoming subscription information
6761 * whether it comes from NOTIFY, BENOTIFY requests or
6762 * piggy-backed to subscription's OK responce.
6764 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6765 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6767 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6769 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6770 const gchar *event = sipmsg_find_header(msg, "Event");
6771 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6772 char *tmp;
6774 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6775 event ? event : "",
6776 tmp = fix_newlines(msg->body));
6777 g_free(tmp);
6778 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
6780 /* implicit subscriptions */
6781 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6782 sipe_process_imdn(sip, msg);
6785 if (event) {
6786 /* for one off subscriptions (send with Expire: 0) */
6787 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
6789 sipe_process_provisioning_v2(sip, msg);
6791 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
6793 sipe_process_provisioning(sip, msg);
6795 else if (sipe_strcase_equal(event, "presence"))
6797 sipe_process_presence(sip, msg);
6799 else if (sipe_strcase_equal(event, "registration-notify"))
6801 sipe_process_registration_notify(sip, msg);
6804 if (!subscription_state || strstr(subscription_state, "active"))
6806 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
6808 sipe_process_roaming_contacts(sip, msg);
6810 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
6812 sipe_process_roaming_self(sip, msg);
6814 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
6816 sipe_process_roaming_acl(sip, msg);
6818 else if (sipe_strcase_equal(event, "presence.wpending"))
6820 sipe_process_presence_wpending(sip, msg);
6822 else if (sipe_strcase_equal(event, "conference"))
6824 sipe_process_conference(sip, msg);
6829 /* The server sends status 'terminated' */
6830 if (subscription_state && strstr(subscription_state, "terminated") ) {
6831 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6832 gchar *key = sipe_get_subscription_key(event, who);
6834 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6835 g_free(who);
6837 if (g_hash_table_lookup(sip->subscriptions, key)) {
6838 g_hash_table_remove(sip->subscriptions, key);
6839 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
6842 g_free(key);
6845 if (!request && event) {
6846 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6847 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6848 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
6850 if (timeout) {
6851 /* 2 min ahead of expiration */
6852 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6854 if (sipe_strcase_equal(event, "presence.wpending") &&
6855 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6857 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6858 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6859 g_free(action_name);
6861 else if (sipe_strcase_equal(event, "presence") &&
6862 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6864 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6865 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6867 if (sip->batched_support) {
6868 sipe_process_presence_timeout(sip, msg, who, timeout);
6870 else {
6871 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6872 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
6874 g_free(action_name);
6875 g_free(who);
6880 /* The client responses on received a NOTIFY message */
6881 if (request && !benotify)
6883 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6888 * Whether user manually changed status or
6889 * it was changed automatically due to user
6890 * became inactive/active again
6892 static gboolean
6893 sipe_is_user_state(struct sipe_account_data *sip)
6895 gboolean res;
6896 time_t now = time(NULL);
6898 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6899 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6901 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6903 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res ? "USER" : "MACHINE");
6904 return res;
6907 static void
6908 send_presence_soap0(struct sipe_account_data *sip,
6909 gboolean do_publish_calendar,
6910 gboolean do_reset_status)
6912 struct sipe_ews* ews = sip->ews;
6913 int availability = 0;
6914 int activity = 0;
6915 gchar *body;
6916 gchar *tmp;
6917 gchar *tmp2 = NULL;
6918 gchar *res_note = NULL;
6919 gchar *res_oof = NULL;
6920 const gchar *note_pub = NULL;
6921 gchar *states = NULL;
6922 gchar *calendar_data = NULL;
6923 gchar *epid = get_epid(sip);
6924 time_t now = time(NULL);
6925 gchar *since_time_str = sipe_utils_time_to_str(now);
6926 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6927 const char *user_input;
6928 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6930 if (oof_note && sip->note) {
6931 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6932 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6935 purple_debug_info("sipe", "sip->note : %s", sip->note ? sip->note : "");
6937 if (!sip->initial_state_published ||
6938 do_reset_status)
6940 g_free(sip->status);
6941 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6944 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6946 /* Note */
6947 if (pub_oof) {
6948 note_pub = oof_note;
6949 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6950 ews->published = TRUE;
6951 } else if (sip->note) {
6952 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6953 g_free(sip->note);
6954 sip->note = NULL;
6955 sip->is_oof_note = FALSE;
6956 sip->note_since = 0;
6957 } else {
6958 note_pub = sip->note;
6959 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6963 if (note_pub)
6965 /* to protocol internal plain text format */
6966 tmp = purple_markup_strip_html(note_pub);
6967 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6968 g_free(tmp);
6971 /* User State */
6972 if (!do_reset_status) {
6973 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6975 gchar *activity_token = NULL;
6976 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6978 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6979 avail_2007,
6980 since_time_str,
6981 epid,
6982 activity_token);
6983 g_free(activity_token);
6985 else /* preserve existing publication */
6987 if (sip->user_states) {
6988 states = g_strdup(sip->user_states);
6991 } else {
6992 /* do nothing - then User state will be erased */
6994 sip->initial_state_published = TRUE;
6996 /* CalendarInfo */
6997 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6999 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7000 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7001 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7002 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7003 fb_start_str,
7004 free_busy_base64);
7005 g_free(fb_start_str);
7006 g_free(free_busy_base64);
7009 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7011 /* forming resulting XML */
7012 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7013 sip->username,
7014 availability,
7015 activity,
7016 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7017 res_note ? res_note : "",
7018 res_oof ? res_oof : "",
7019 states ? states : "",
7020 calendar_data ? calendar_data : "",
7021 epid,
7022 since_time_str,
7023 since_time_str,
7024 user_input);
7025 g_free(tmp);
7026 g_free(tmp2);
7027 g_free(res_note);
7028 g_free(states);
7029 g_free(calendar_data);
7031 send_soap_request(sip, body);
7033 g_free(body);
7034 g_free(since_time_str);
7035 g_free(epid);
7038 void
7039 send_presence_soap(struct sipe_account_data *sip,
7040 gboolean do_publish_calendar)
7042 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7046 static gboolean
7047 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7048 struct sipmsg *msg,
7049 struct transaction *trans)
7051 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7053 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7054 xmlnode *xml;
7055 xmlnode *node;
7056 gchar *fault_code;
7057 GHashTable *faults;
7058 int index_our;
7059 gboolean has_device_publication = FALSE;
7061 xml = xmlnode_from_str(msg->body, msg->bodylen);
7063 /* test if version mismatch fault */
7064 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7065 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7066 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
7067 g_free(fault_code);
7068 xmlnode_free(xml);
7069 return TRUE;
7071 g_free(fault_code);
7073 /* accumulating information about faulty versions */
7074 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7075 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7076 node;
7077 node = xmlnode_get_next_twin(node))
7079 const gchar *index = xmlnode_get_attrib(node, "index");
7080 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7082 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7083 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
7085 xmlnode_free(xml);
7087 /* here we are parsing own request to figure out what publication
7088 * referensed here only by index went wrong
7090 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7092 /* publication */
7093 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7094 index_our = 1; /* starts with 1 - our first publication */
7095 node;
7096 node = xmlnode_get_next_twin(node), index_our++)
7098 gchar *idx = g_strdup_printf("%d", index_our);
7099 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7100 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7101 g_free(idx);
7103 if (sipe_strequal("device", categoryName)) {
7104 has_device_publication = TRUE;
7107 if (curVersion) { /* fault exist on this index */
7108 const gchar *container = xmlnode_get_attrib(node, "container");
7109 const gchar *instance = xmlnode_get_attrib(node, "instance");
7110 /* key is <category><instance><container> */
7111 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7112 struct sipe_publication *publication =
7113 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7115 purple_debug_info("sipe", "key is %s\n", key);
7117 if (publication) {
7118 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7119 key, curVersion, publication->version);
7120 /* updating publication's version to the correct one */
7121 publication->version = atoi(curVersion);
7123 g_free(key);
7126 xmlnode_free(xml);
7127 g_hash_table_destroy(faults);
7129 /* rebublishing with right versions */
7130 if (has_device_publication) {
7131 send_publish_category_initial(sip);
7132 } else {
7133 send_presence_status(sip);
7136 return TRUE;
7140 * Returns 'device' XML part for publication.
7141 * Must be g_free'd after use.
7143 static gchar *
7144 sipe_publish_get_category_device(struct sipe_account_data *sip)
7146 gchar *uri;
7147 gchar *doc;
7148 gchar *epid = get_epid(sip);
7149 gchar *uuid = generateUUIDfromEPID(epid);
7150 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7151 /* key is <category><instance><container> */
7152 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7153 struct sipe_publication *publication =
7154 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7156 g_free(key);
7157 g_free(epid);
7159 uri = sip_uri_self(sip);
7160 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7161 device_instance,
7162 publication ? publication->version : 0,
7163 uuid,
7164 uri,
7165 "00:00:00+01:00", /* @TODO make timezone real*/
7166 g_get_host_name()
7169 g_free(uri);
7170 g_free(uuid);
7172 return doc;
7176 * A service method - use
7177 * - send_publish_get_category_state_machine and
7178 * - send_publish_get_category_state_user instead.
7179 * Must be g_free'd after use.
7181 static gchar *
7182 sipe_publish_get_category_state(struct sipe_account_data *sip,
7183 gboolean is_user_state)
7185 int availability = sipe_get_availability_by_status(sip->status, NULL);
7186 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7187 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7188 /* key is <category><instance><container> */
7189 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7190 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7191 struct sipe_publication *publication_2 =
7192 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7193 struct sipe_publication *publication_3 =
7194 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7196 g_free(key_2);
7197 g_free(key_3);
7199 if (publication_2 && (publication_2->availability == availability))
7201 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7202 return NULL; /* nothing to update */
7205 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7206 instance,
7207 publication_2 ? publication_2->version : 0,
7208 availability,
7209 instance,
7210 publication_3 ? publication_3->version : 0,
7211 availability);
7215 * Only Busy and OOF calendar event are published.
7216 * Different instances are used for that.
7218 * Must be g_free'd after use.
7220 static gchar *
7221 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7222 struct sipe_cal_event *event,
7223 const char *uri,
7224 int cal_satus)
7226 gchar *start_time_str;
7227 int availability = 0;
7228 gchar *res;
7229 gchar *tmp = NULL;
7230 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7231 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7232 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7234 /* key is <category><instance><container> */
7235 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7236 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7237 struct sipe_publication *publication_2 =
7238 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7239 struct sipe_publication *publication_3 =
7240 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7242 g_free(key_2);
7243 g_free(key_3);
7245 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7246 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7247 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7248 return NULL;
7251 if (event &&
7252 publication_3 &&
7253 (publication_3->availability == availability) &&
7254 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7256 g_free(tmp);
7257 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7258 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7259 return NULL; /* nothing to update */
7261 g_free(tmp);
7263 if (event &&
7264 (event->cal_status == SIPE_CAL_BUSY ||
7265 event->cal_status == SIPE_CAL_OOF))
7267 gchar *availability_xml_str = NULL;
7268 gchar *activity_xml_str = NULL;
7270 if (event->cal_status == SIPE_CAL_BUSY) {
7271 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7274 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7275 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7276 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7277 "minAvailability=\"6500\"",
7278 "maxAvailability=\"8999\"");
7279 } else if (event->cal_status == SIPE_CAL_OOF) {
7280 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7281 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7282 "minAvailability=\"12000\"",
7283 "");
7285 start_time_str = sipe_utils_time_to_str(event->start_time);
7287 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7288 instance,
7289 publication_2 ? publication_2->version : 0,
7290 uri,
7291 start_time_str,
7292 availability_xml_str ? availability_xml_str : "",
7293 activity_xml_str ? activity_xml_str : "",
7294 event->subject ? event->subject : "",
7295 event->location ? event->location : "",
7297 instance,
7298 publication_3 ? publication_3->version : 0,
7299 uri,
7300 start_time_str,
7301 availability_xml_str ? availability_xml_str : "",
7302 activity_xml_str ? activity_xml_str : "",
7303 event->subject ? event->subject : "",
7304 event->location ? event->location : ""
7306 g_free(start_time_str);
7307 g_free(availability_xml_str);
7308 g_free(activity_xml_str);
7311 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7313 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7314 instance,
7315 publication_2 ? publication_2->version : 0,
7317 instance,
7318 publication_3 ? publication_3->version : 0
7322 return res;
7326 * Returns 'machineState' XML part for publication.
7327 * Must be g_free'd after use.
7329 static gchar *
7330 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7332 return sipe_publish_get_category_state(sip, FALSE);
7336 * Returns 'userState' XML part for publication.
7337 * Must be g_free'd after use.
7339 static gchar *
7340 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7342 return sipe_publish_get_category_state(sip, TRUE);
7346 * Returns 'note' XML part for publication.
7347 * Must be g_free'd after use.
7349 * Protocol format for Note is plain text.
7351 * @param note a note in Sipe internal HTML format
7352 * @param note_type either personal or OOF
7354 static gchar *
7355 sipe_publish_get_category_note(struct sipe_account_data *sip,
7356 const char *note, /* html */
7357 const char *note_type,
7358 time_t note_start,
7359 time_t note_end)
7361 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7362 /* key is <category><instance><container> */
7363 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7364 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7365 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7367 struct sipe_publication *publication_note_200 =
7368 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7369 struct sipe_publication *publication_note_300 =
7370 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7371 struct sipe_publication *publication_note_400 =
7372 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7374 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7375 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7376 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7377 char *res, *tmp1, *tmp2, *tmp3;
7378 char *start_time_attr;
7379 char *end_time_attr;
7381 g_free(tmp);
7382 tmp = NULL;
7383 g_free(key_note_200);
7384 g_free(key_note_300);
7385 g_free(key_note_400);
7387 /* we even need to republish empty note */
7388 if (sipe_strequal(n1, n2))
7390 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7391 g_free(n1);
7392 return NULL; /* nothing to update */
7395 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7396 g_free(tmp);
7397 tmp = NULL;
7398 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7399 g_free(tmp);
7401 if (n1) {
7402 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7403 instance,
7404 200,
7405 publication_note_200 ? publication_note_200->version : 0,
7406 note_type,
7407 start_time_attr ? start_time_attr : "",
7408 end_time_attr ? end_time_attr : "",
7409 n1);
7411 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7412 instance,
7413 300,
7414 publication_note_300 ? publication_note_300->version : 0,
7415 note_type,
7416 start_time_attr ? start_time_attr : "",
7417 end_time_attr ? end_time_attr : "",
7418 n1);
7420 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7421 instance,
7422 400,
7423 publication_note_400 ? publication_note_400->version : 0,
7424 note_type,
7425 start_time_attr ? start_time_attr : "",
7426 end_time_attr ? end_time_attr : "",
7427 n1);
7428 } else {
7429 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7430 "note",
7431 instance,
7432 200,
7433 publication_note_200 ? publication_note_200->version : 0,
7434 "static");
7435 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7436 "note",
7437 instance,
7438 300,
7439 publication_note_200 ? publication_note_200->version : 0,
7440 "static");
7441 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7442 "note",
7443 instance,
7444 400,
7445 publication_note_200 ? publication_note_200->version : 0,
7446 "static");
7448 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7450 g_free(start_time_attr);
7451 g_free(end_time_attr);
7452 g_free(tmp1);
7453 g_free(tmp2);
7454 g_free(tmp3);
7455 g_free(n1);
7457 return res;
7461 * Returns 'calendarData' XML part with WorkingHours for publication.
7462 * Must be g_free'd after use.
7464 static gchar *
7465 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7467 struct sipe_ews* ews = sip->ews;
7469 /* key is <category><instance><container> */
7470 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7471 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7472 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7473 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7474 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7475 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7477 struct sipe_publication *publication_cal_1 =
7478 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7479 struct sipe_publication *publication_cal_100 =
7480 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7481 struct sipe_publication *publication_cal_200 =
7482 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7483 struct sipe_publication *publication_cal_300 =
7484 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7485 struct sipe_publication *publication_cal_400 =
7486 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7487 struct sipe_publication *publication_cal_32000 =
7488 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7490 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7491 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7493 g_free(key_cal_1);
7494 g_free(key_cal_100);
7495 g_free(key_cal_200);
7496 g_free(key_cal_300);
7497 g_free(key_cal_400);
7498 g_free(key_cal_32000);
7500 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7501 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7502 return NULL;
7505 if (sipe_strequal(n1, n2))
7507 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7508 return NULL; /* nothing to update */
7511 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7512 /* 1 */
7513 publication_cal_1 ? publication_cal_1->version : 0,
7514 ews->email,
7515 ews->working_hours_xml_str,
7516 /* 100 - Public */
7517 publication_cal_100 ? publication_cal_100->version : 0,
7518 /* 200 - Company */
7519 publication_cal_200 ? publication_cal_200->version : 0,
7520 ews->email,
7521 ews->working_hours_xml_str,
7522 /* 300 - Team */
7523 publication_cal_300 ? publication_cal_300->version : 0,
7524 ews->email,
7525 ews->working_hours_xml_str,
7526 /* 400 - Personal */
7527 publication_cal_400 ? publication_cal_400->version : 0,
7528 ews->email,
7529 ews->working_hours_xml_str,
7530 /* 32000 - Blocked */
7531 publication_cal_32000 ? publication_cal_32000->version : 0
7536 * Returns 'calendarData' XML part with FreeBusy for publication.
7537 * Must be g_free'd after use.
7539 static gchar *
7540 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7542 struct sipe_ews* ews = sip->ews;
7543 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7544 char *fb_start_str;
7545 char *free_busy_base64;
7546 const char *st;
7547 const char *fb;
7548 char *res;
7550 /* key is <category><instance><container> */
7551 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7552 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7553 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7554 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7555 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7556 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7558 struct sipe_publication *publication_cal_1 =
7559 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7560 struct sipe_publication *publication_cal_100 =
7561 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7562 struct sipe_publication *publication_cal_200 =
7563 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7564 struct sipe_publication *publication_cal_300 =
7565 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7566 struct sipe_publication *publication_cal_400 =
7567 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7568 struct sipe_publication *publication_cal_32000 =
7569 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7571 g_free(key_cal_1);
7572 g_free(key_cal_100);
7573 g_free(key_cal_200);
7574 g_free(key_cal_300);
7575 g_free(key_cal_400);
7576 g_free(key_cal_32000);
7578 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7579 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7580 return NULL;
7583 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7584 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7586 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7587 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7589 /* we will rebuplish the same data to refresh publication time,
7590 * so if data from multiple sources, most recent will be choosen
7592 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7594 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7595 // g_free(fb_start_str);
7596 // g_free(free_busy_base64);
7597 // return NULL; /* nothing to update */
7600 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7601 /* 1 */
7602 cal_data_instance,
7603 publication_cal_1 ? publication_cal_1->version : 0,
7604 /* 100 - Public */
7605 cal_data_instance,
7606 publication_cal_100 ? publication_cal_100->version : 0,
7607 /* 200 - Company */
7608 cal_data_instance,
7609 publication_cal_200 ? publication_cal_200->version : 0,
7610 ews->email,
7611 fb_start_str,
7612 free_busy_base64,
7613 /* 300 - Team */
7614 cal_data_instance,
7615 publication_cal_300 ? publication_cal_300->version : 0,
7616 ews->email,
7617 fb_start_str,
7618 free_busy_base64,
7619 /* 400 - Personal */
7620 cal_data_instance,
7621 publication_cal_400 ? publication_cal_400->version : 0,
7622 ews->email,
7623 fb_start_str,
7624 free_busy_base64,
7625 /* 32000 - Blocked */
7626 cal_data_instance,
7627 publication_cal_32000 ? publication_cal_32000->version : 0
7630 g_free(fb_start_str);
7631 g_free(free_busy_base64);
7632 return res;
7635 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7637 gchar *uri;
7638 gchar *doc;
7639 gchar *tmp;
7640 gchar *hdr;
7642 uri = sip_uri_self(sip);
7643 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7644 uri,
7645 publications);
7647 tmp = get_contact(sip);
7648 hdr = g_strdup_printf("Contact: %s\r\n"
7649 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7651 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7653 g_free(tmp);
7654 g_free(hdr);
7655 g_free(uri);
7656 g_free(doc);
7659 static void
7660 send_publish_category_initial(struct sipe_account_data *sip)
7662 gchar *pub_device = sipe_publish_get_category_device(sip);
7663 gchar *pub_machine;
7664 gchar *publications;
7666 g_free(sip->status);
7667 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7669 pub_machine = sipe_publish_get_category_state_machine(sip);
7670 publications = g_strdup_printf("%s%s",
7671 pub_device,
7672 pub_machine ? pub_machine : "");
7673 g_free(pub_device);
7674 g_free(pub_machine);
7676 send_presence_publish(sip, publications);
7677 g_free(publications);
7680 static void
7681 send_presence_category_publish(struct sipe_account_data *sip)
7683 gchar *pub_state = sipe_is_user_state(sip) ?
7684 sipe_publish_get_category_state_user(sip) :
7685 sipe_publish_get_category_state_machine(sip);
7686 gchar *pub_note = sipe_publish_get_category_note(sip,
7687 sip->note,
7688 sip->is_oof_note ? "OOF" : "personal",
7691 gchar *publications;
7693 if (!pub_state && !pub_note) {
7694 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7695 return;
7698 publications = g_strdup_printf("%s%s",
7699 pub_state ? pub_state : "",
7700 pub_note ? pub_note : "");
7702 g_free(pub_state);
7703 g_free(pub_note);
7705 send_presence_publish(sip, publications);
7706 g_free(publications);
7710 * Publishes self status
7711 * based on own calendar information.
7713 * For 2007+
7715 void
7716 publish_calendar_status_self(struct sipe_account_data *sip)
7718 struct sipe_cal_event* event = NULL;
7719 gchar *pub_cal_working_hours = NULL;
7720 gchar *pub_cal_free_busy = NULL;
7721 gchar *pub_calendar = NULL;
7722 gchar *pub_calendar2 = NULL;
7723 gchar *pub_oof_note = NULL;
7724 const gchar *oof_note;
7725 time_t oof_start = 0;
7726 time_t oof_end = 0;
7728 if (!sip->ews) {
7729 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7730 return;
7733 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7734 if (sip->ews->cal_events) {
7735 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7738 if (!event) {
7739 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7740 } else {
7741 char *desc = sipe_cal_event_describe(event);
7742 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7743 g_free(desc);
7746 /* Logic
7747 if OOF
7748 OOF publish, Busy clean
7749 ilse if Busy
7750 OOF clean, Busy publish
7751 else
7752 OOF clean, Busy clean
7754 if (event && event->cal_status == SIPE_CAL_OOF) {
7755 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7756 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7757 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7758 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7759 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7760 } else {
7761 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7762 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7765 oof_note = sipe_ews_get_oof_note(sip->ews);
7766 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7767 oof_start = sip->ews->oof_start;
7768 oof_end = sip->ews->oof_end;
7770 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7772 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7773 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7775 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7776 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7777 } else {
7778 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7779 pub_cal_working_hours ? pub_cal_working_hours : "",
7780 pub_cal_free_busy ? pub_cal_free_busy : "",
7781 pub_calendar ? pub_calendar : "",
7782 pub_calendar2 ? pub_calendar2 : "",
7783 pub_oof_note ? pub_oof_note : "");
7785 send_presence_publish(sip, publications);
7786 g_free(publications);
7789 g_free(pub_cal_working_hours);
7790 g_free(pub_cal_free_busy);
7791 g_free(pub_calendar);
7792 g_free(pub_calendar2);
7793 g_free(pub_oof_note);
7795 /* repeat scheduling */
7796 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7799 static void send_presence_status(struct sipe_account_data *sip)
7801 PurpleStatus * status = purple_account_get_active_status(sip->account);
7803 if (!status) return;
7805 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7806 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7807 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7809 if (sip->ocs2007) {
7810 send_presence_category_publish(sip);
7811 } else {
7812 send_presence_soap(sip, FALSE);
7816 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7818 gboolean found = FALSE;
7819 const char *method = msg->method ? msg->method : "NOT FOUND";
7820 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7821 if (msg->response == 0) { /* request */
7822 if (sipe_strequal(method, "MESSAGE")) {
7823 process_incoming_message(sip, msg);
7824 found = TRUE;
7825 } else if (sipe_strequal(method, "NOTIFY")) {
7826 purple_debug_info("sipe","send->process_incoming_notify\n");
7827 process_incoming_notify(sip, msg, TRUE, FALSE);
7828 found = TRUE;
7829 } else if (sipe_strequal(method, "BENOTIFY")) {
7830 purple_debug_info("sipe","send->process_incoming_benotify\n");
7831 process_incoming_notify(sip, msg, TRUE, TRUE);
7832 found = TRUE;
7833 } else if (sipe_strequal(method, "INVITE")) {
7834 process_incoming_invite(sip, msg);
7835 found = TRUE;
7836 } else if (sipe_strequal(method, "REFER")) {
7837 process_incoming_refer(sip, msg);
7838 found = TRUE;
7839 } else if (sipe_strequal(method, "OPTIONS")) {
7840 process_incoming_options(sip, msg);
7841 found = TRUE;
7842 } else if (sipe_strequal(method, "INFO")) {
7843 process_incoming_info(sip, msg);
7844 found = TRUE;
7845 } else if (sipe_strequal(method, "ACK")) {
7846 // ACK's don't need any response
7847 found = TRUE;
7848 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7849 // LCS 2005 sends us these - just respond 200 OK
7850 found = TRUE;
7851 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7852 } else if (sipe_strequal(method, "BYE")) {
7853 process_incoming_bye(sip, msg);
7854 found = TRUE;
7855 } else {
7856 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7858 } else { /* response */
7859 struct transaction *trans = transactions_find(sip, msg);
7860 if (trans) {
7861 if (msg->response == 407) {
7862 gchar *resend, *auth;
7863 const gchar *ptmp;
7865 if (sip->proxy.retries > 30) return;
7866 sip->proxy.retries++;
7867 /* do proxy authentication */
7869 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7871 fill_auth(ptmp, &sip->proxy);
7872 auth = auth_header(sip, &sip->proxy, trans->msg);
7873 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7874 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7875 g_free(auth);
7876 resend = sipmsg_to_string(trans->msg);
7877 /* resend request */
7878 sendout_pkt(sip->gc, resend);
7879 g_free(resend);
7880 } else {
7881 if (msg->response < 200) {
7882 /* ignore provisional response */
7883 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7884 } else {
7885 sip->proxy.retries = 0;
7886 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7887 if (msg->response == 401)
7889 sip->registrar.retries++;
7891 else
7893 sip->registrar.retries = 0;
7895 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7896 } else {
7897 if (msg->response == 401) {
7898 gchar *resend, *auth, *ptmp;
7899 const char* auth_scheme;
7901 if (sip->registrar.retries > 4) return;
7902 sip->registrar.retries++;
7904 auth_scheme = sipe_get_auth_scheme_name(sip);
7905 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7907 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7908 if (!ptmp) {
7909 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7910 sip->gc->wants_to_die = TRUE;
7911 purple_connection_error(sip->gc, tmp2);
7912 g_free(tmp2);
7913 return;
7916 fill_auth(ptmp, &sip->registrar);
7917 auth = auth_header(sip, &sip->registrar, trans->msg);
7918 sipmsg_remove_header_now(trans->msg, "Authorization");
7919 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7920 g_free(auth);
7921 resend = sipmsg_to_string(trans->msg);
7922 /* resend request */
7923 sendout_pkt(sip->gc, resend);
7924 g_free(resend);
7928 if (trans->callback) {
7929 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7930 /* call the callback to process response*/
7931 (trans->callback)(sip, msg, trans);
7934 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7935 transactions_remove(sip, trans);
7939 found = TRUE;
7940 } else {
7941 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7944 if (!found) {
7945 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7949 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7951 char *cur;
7952 char *dummy;
7953 char *tmp;
7954 struct sipmsg *msg;
7955 int restlen;
7956 cur = conn->inbuf;
7958 /* according to the RFC remove CRLF at the beginning */
7959 while (*cur == '\r' || *cur == '\n') {
7960 cur++;
7962 if (cur != conn->inbuf) {
7963 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7964 conn->inbufused = strlen(conn->inbuf);
7967 /* Received a full Header? */
7968 sip->processing_input = TRUE;
7969 while (sip->processing_input &&
7970 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7971 time_t currtime = time(NULL);
7972 cur += 2;
7973 cur[0] = '\0';
7974 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7975 g_free(tmp);
7976 msg = sipmsg_parse_header(conn->inbuf);
7977 cur[0] = '\r';
7978 cur += 2;
7979 restlen = conn->inbufused - (cur - conn->inbuf);
7980 if (msg && restlen >= msg->bodylen) {
7981 dummy = g_malloc(msg->bodylen + 1);
7982 memcpy(dummy, cur, msg->bodylen);
7983 dummy[msg->bodylen] = '\0';
7984 msg->body = dummy;
7985 cur += msg->bodylen;
7986 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7987 conn->inbufused = strlen(conn->inbuf);
7988 } else {
7989 if (msg){
7990 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7991 sipmsg_free(msg);
7993 return;
7996 /*if (msg->body) {
7997 purple_debug_info("sipe", "body:\n%s", msg->body);
8000 // Verify the signature before processing it
8001 if (sip->registrar.gssapi_context) {
8002 struct sipmsg_breakdown msgbd;
8003 gchar *signature_input_str;
8004 gchar *rspauth;
8005 msgbd.msg = msg;
8006 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8007 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8009 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8011 if (rspauth != NULL) {
8012 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8013 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
8014 process_input_message(sip, msg);
8015 } else {
8016 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
8017 purple_connection_error(sip->gc, _("Invalid message signature received"));
8018 sip->gc->wants_to_die = TRUE;
8020 } else if (msg->response == 401) {
8021 purple_connection_error(sip->gc, _("Authentication failed"));
8022 sip->gc->wants_to_die = TRUE;
8024 g_free(signature_input_str);
8026 g_free(rspauth);
8027 sipmsg_breakdown_free(&msgbd);
8028 } else {
8029 process_input_message(sip, msg);
8032 sipmsg_free(msg);
8036 static void sipe_udp_process(gpointer data, gint source,
8037 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8039 PurpleConnection *gc = data;
8040 struct sipe_account_data *sip = gc->proto_data;
8041 int len;
8043 static char buffer[65536];
8044 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8045 time_t currtime = time(NULL);
8046 struct sipmsg *msg;
8047 buffer[len] = '\0';
8048 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
8049 msg = sipmsg_parse_msg(buffer);
8050 if (msg) process_input_message(sip, msg);
8054 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8056 struct sipe_account_data *sip = gc->proto_data;
8057 PurpleSslConnection *gsc = sip->gsc;
8059 purple_debug_error("sipe", "%s",debug);
8060 purple_connection_error(gc, msg);
8062 /* Invalidate this connection. Next send will open a new one */
8063 if (gsc) {
8064 connection_remove(sip, gsc->fd);
8065 purple_ssl_close(gsc);
8067 sip->gsc = NULL;
8068 sip->fd = -1;
8071 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8072 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8074 PurpleConnection *gc = data;
8075 struct sipe_account_data *sip;
8076 struct sip_connection *conn;
8077 int readlen, len;
8078 gboolean firstread = TRUE;
8080 /* NOTE: This check *IS* necessary */
8081 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8082 purple_ssl_close(gsc);
8083 return;
8086 sip = gc->proto_data;
8087 conn = connection_find(sip, gsc->fd);
8088 if (conn == NULL) {
8089 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8090 gc->wants_to_die = TRUE;
8091 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8092 return;
8095 /* Read all available data from the SSL connection */
8096 do {
8097 /* Increase input buffer size as needed */
8098 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8099 conn->inbuflen += SIMPLE_BUF_INC;
8100 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8101 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8104 /* Try to read as much as there is space left in the buffer */
8105 readlen = conn->inbuflen - conn->inbufused - 1;
8106 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8108 if (len < 0 && errno == EAGAIN) {
8109 /* Try again later */
8110 return;
8111 } else if (len < 0) {
8112 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8113 return;
8114 } else if (firstread && (len == 0)) {
8115 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8116 return;
8119 conn->inbufused += len;
8120 firstread = FALSE;
8122 /* Equivalence indicates that there is possibly more data to read */
8123 } while (len == readlen);
8125 conn->inbuf[conn->inbufused] = '\0';
8126 process_input(sip, conn);
8130 static void sipe_input_cb(gpointer data, gint source,
8131 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8133 PurpleConnection *gc = data;
8134 struct sipe_account_data *sip = gc->proto_data;
8135 int len;
8136 struct sip_connection *conn = connection_find(sip, source);
8137 if (!conn) {
8138 purple_debug_error("sipe", "Connection not found!\n");
8139 return;
8142 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8143 conn->inbuflen += SIMPLE_BUF_INC;
8144 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8147 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8149 if (len < 0 && errno == EAGAIN)
8150 return;
8151 else if (len <= 0) {
8152 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8153 connection_remove(sip, source);
8154 if (sip->fd == source) sip->fd = -1;
8155 return;
8158 conn->inbufused += len;
8159 conn->inbuf[conn->inbufused] = '\0';
8161 process_input(sip, conn);
8164 /* Callback for new connections on incoming TCP port */
8165 static void sipe_newconn_cb(gpointer data, gint source,
8166 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8168 PurpleConnection *gc = data;
8169 struct sipe_account_data *sip = gc->proto_data;
8170 struct sip_connection *conn;
8172 int newfd = accept(source, NULL, NULL);
8174 conn = connection_create(sip, newfd);
8176 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8179 static void login_cb(gpointer data, gint source,
8180 SIPE_UNUSED_PARAMETER const gchar *error_message)
8182 PurpleConnection *gc = data;
8183 struct sipe_account_data *sip;
8184 struct sip_connection *conn;
8186 if (!PURPLE_CONNECTION_IS_VALID(gc))
8188 if (source >= 0)
8189 close(source);
8190 return;
8193 if (source < 0) {
8194 purple_connection_error(gc, _("Could not connect"));
8195 return;
8198 sip = gc->proto_data;
8199 sip->fd = source;
8200 sip->last_keepalive = time(NULL);
8202 conn = connection_create(sip, source);
8204 do_register(sip);
8206 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8209 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8210 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8212 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8213 if (sip == NULL) return;
8215 do_register(sip);
8218 static guint sipe_ht_hash_nick(const char *nick)
8220 char *lc = g_utf8_strdown(nick, -1);
8221 guint bucket = g_str_hash(lc);
8222 g_free(lc);
8224 return bucket;
8227 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8229 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8232 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8234 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8236 sip->listen_data = NULL;
8238 if (listenfd == -1) {
8239 purple_connection_error(sip->gc, _("Could not create listen socket"));
8240 return;
8243 sip->fd = listenfd;
8245 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8246 sip->listenfd = sip->fd;
8248 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8250 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8251 do_register(sip);
8254 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8255 SIPE_UNUSED_PARAMETER const char *error_message)
8257 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8259 sip->query_data = NULL;
8261 if (!hosts || !hosts->data) {
8262 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8263 return;
8266 hosts = g_slist_remove(hosts, hosts->data);
8267 g_free(sip->serveraddr);
8268 sip->serveraddr = hosts->data;
8269 hosts = g_slist_remove(hosts, hosts->data);
8270 while (hosts) {
8271 void *tmp = hosts->data;
8272 hosts = g_slist_remove(hosts, tmp);
8273 hosts = g_slist_remove(hosts, tmp);
8274 g_free(tmp);
8277 /* create socket for incoming connections */
8278 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8279 sipe_udp_host_resolved_listen_cb, sip);
8280 if (sip->listen_data == NULL) {
8281 purple_connection_error(sip->gc, _("Could not create listen socket"));
8282 return;
8286 static const struct sipe_service_data *current_service = NULL;
8288 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8289 PurpleSslErrorType error,
8290 gpointer data)
8292 PurpleConnection *gc = data;
8293 struct sipe_account_data *sip;
8295 /* If the connection is already disconnected, we don't need to do anything else */
8296 if (!PURPLE_CONNECTION_IS_VALID(gc))
8297 return;
8299 sip = gc->proto_data;
8300 current_service = sip->service_data;
8301 if (current_service) {
8302 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8303 current_service->transport ? current_service->transport : "NULL",
8304 current_service->service ? current_service->service : "NULL");
8307 sip->fd = -1;
8308 sip->gsc = NULL;
8310 switch(error) {
8311 case PURPLE_SSL_CONNECT_FAILED:
8312 purple_connection_error(gc, _("Connection failed"));
8313 break;
8314 case PURPLE_SSL_HANDSHAKE_FAILED:
8315 purple_connection_error(gc, _("SSL handshake failed"));
8316 break;
8317 case PURPLE_SSL_CERTIFICATE_INVALID:
8318 purple_connection_error(gc, _("SSL certificate invalid"));
8319 break;
8323 static void
8324 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8326 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8327 PurpleProxyConnectData *connect_data;
8329 sip->listen_data = NULL;
8331 sip->listenfd = listenfd;
8332 if (sip->listenfd == -1) {
8333 purple_connection_error(sip->gc, _("Could not create listen socket"));
8334 return;
8337 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8338 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8339 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8340 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8341 sipe_newconn_cb, sip->gc);
8342 purple_debug_info("sipe", "connecting to %s port %d\n",
8343 sip->realhostname, sip->realport);
8344 /* open tcp connection to the server */
8345 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8346 sip->realport, login_cb, sip->gc);
8348 if (connect_data == NULL) {
8349 purple_connection_error(sip->gc, _("Could not create socket"));
8353 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8355 PurpleAccount *account = sip->account;
8356 PurpleConnection *gc = sip->gc;
8358 if (port == 0) {
8359 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8362 sip->realhostname = hostname;
8363 sip->realport = port;
8365 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8366 hostname, port);
8368 /* TODO: is there a good default grow size? */
8369 if (sip->transport != SIPE_TRANSPORT_UDP)
8370 sip->txbuf = purple_circ_buffer_new(0);
8372 if (sip->transport == SIPE_TRANSPORT_TLS) {
8373 /* SSL case */
8374 if (!purple_ssl_is_supported()) {
8375 gc->wants_to_die = TRUE;
8376 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8377 return;
8380 purple_debug_info("sipe", "using SSL\n");
8382 sip->gsc = purple_ssl_connect(account, hostname, port,
8383 login_cb_ssl, sipe_ssl_connect_failure, gc);
8384 if (sip->gsc == NULL) {
8385 purple_connection_error(gc, _("Could not create SSL context"));
8386 return;
8388 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8389 /* UDP case */
8390 purple_debug_info("sipe", "using UDP\n");
8392 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8393 if (sip->query_data == NULL) {
8394 purple_connection_error(gc, _("Could not resolve hostname"));
8396 } else {
8397 /* TCP case */
8398 purple_debug_info("sipe", "using TCP\n");
8399 /* create socket for incoming connections */
8400 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8401 sipe_tcp_connect_listen_cb, sip);
8402 if (sip->listen_data == NULL) {
8403 purple_connection_error(gc, _("Could not create listen socket"));
8404 return;
8409 /* Service list for autodection */
8410 static const struct sipe_service_data service_autodetect[] = {
8411 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8412 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8413 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8414 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8415 { NULL, NULL, 0 }
8418 /* Service list for SSL/TLS */
8419 static const struct sipe_service_data service_tls[] = {
8420 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8421 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8422 { NULL, NULL, 0 }
8425 /* Service list for TCP */
8426 static const struct sipe_service_data service_tcp[] = {
8427 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8428 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8429 { NULL, NULL, 0 }
8432 /* Service list for UDP */
8433 static const struct sipe_service_data service_udp[] = {
8434 { "sip", "udp", SIPE_TRANSPORT_UDP },
8435 { NULL, NULL, 0 }
8438 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8439 static void resolve_next_service(struct sipe_account_data *sip,
8440 const struct sipe_service_data *start)
8442 if (start) {
8443 sip->service_data = start;
8444 } else {
8445 sip->service_data++;
8446 if (sip->service_data->service == NULL) {
8447 gchar *hostname;
8448 /* Try connecting to the SIP hostname directly */
8449 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8450 if (sip->auto_transport) {
8451 // If SSL is supported, default to using it; OCS servers aren't configured
8452 // by default to accept TCP
8453 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8454 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8455 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8458 hostname = g_strdup(sip->sipdomain);
8459 create_connection(sip, hostname, 0);
8460 return;
8464 /* Try to resolve next service */
8465 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8466 sip->service_data->transport,
8467 sip->sipdomain,
8468 srvresolved, sip);
8471 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8473 struct sipe_account_data *sip = data;
8475 sip->srv_query_data = NULL;
8477 /* find the host to connect to */
8478 if (results) {
8479 gchar *hostname = g_strdup(resp->hostname);
8480 int port = resp->port;
8481 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8482 hostname, port);
8483 g_free(resp);
8485 sip->transport = sip->service_data->type;
8487 create_connection(sip, hostname, port);
8488 } else {
8489 resolve_next_service(sip, NULL);
8493 static void sipe_login(PurpleAccount *account)
8495 PurpleConnection *gc;
8496 struct sipe_account_data *sip;
8497 gchar **signinname_login, **userserver;
8498 const char *transport;
8499 const char *email;
8501 const char *username = purple_account_get_username(account);
8502 gc = purple_account_get_connection(account);
8504 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8506 if (strpbrk(username, "\t\v\r\n") != NULL) {
8507 gc->wants_to_die = TRUE;
8508 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8509 return;
8512 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8513 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8514 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8515 sip->gc = gc;
8516 sip->account = account;
8517 sip->reregister_set = FALSE;
8518 sip->reauthenticate_set = FALSE;
8519 sip->subscribed = FALSE;
8520 sip->subscribed_buddies = FALSE;
8521 sip->initial_state_published = FALSE;
8523 /* username format: <username>,[<optional login>] */
8524 signinname_login = g_strsplit(username, ",", 2);
8525 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8527 /* ensure that username format is name@domain */
8528 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8529 g_strfreev(signinname_login);
8530 gc->wants_to_die = TRUE;
8531 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8532 return;
8534 sip->username = g_strdup(signinname_login[0]);
8536 /* ensure that email format is name@domain if provided */
8537 email = purple_account_get_string(sip->account, "email", NULL);
8538 if (!is_empty(email) &&
8539 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8541 gc->wants_to_die = TRUE;
8542 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8543 return;
8545 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8547 /* login name specified? */
8548 if (signinname_login[1] && strlen(signinname_login[1])) {
8549 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8550 gboolean has_domain = domain_user[1] != NULL;
8551 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8552 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8553 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8554 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8555 sip->authdomain ? sip->authdomain : "", sip->authuser);
8556 g_strfreev(domain_user);
8559 userserver = g_strsplit(signinname_login[0], "@", 2);
8560 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8561 purple_connection_set_display_name(gc, userserver[0]);
8562 sip->sipdomain = g_strdup(userserver[1]);
8563 g_strfreev(userserver);
8564 g_strfreev(signinname_login);
8566 if (strchr(sip->username, ' ') != NULL) {
8567 gc->wants_to_die = TRUE;
8568 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8569 return;
8572 sip->password = g_strdup(purple_connection_get_password(gc));
8574 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8575 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8576 g_free, (GDestroyNotify)g_hash_table_destroy);
8577 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8578 g_free, (GDestroyNotify)sipe_subscription_free);
8580 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8582 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8584 g_free(sip->status);
8585 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8587 sip->auto_transport = FALSE;
8588 transport = purple_account_get_string(account, "transport", "auto");
8589 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8590 if (userserver[0]) {
8591 /* Use user specified server[:port] */
8592 int port = 0;
8594 if (userserver[1])
8595 port = atoi(userserver[1]);
8597 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8598 userserver[0], port);
8600 if (sipe_strequal(transport, "auto")) {
8601 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8602 } else if (sipe_strequal(transport, "tls")) {
8603 sip->transport = SIPE_TRANSPORT_TLS;
8604 } else if (sipe_strequal(transport, "tcp")) {
8605 sip->transport = SIPE_TRANSPORT_TCP;
8606 } else {
8607 sip->transport = SIPE_TRANSPORT_UDP;
8610 create_connection(sip, g_strdup(userserver[0]), port);
8611 } else {
8612 /* Server auto-discovery */
8613 if (sipe_strequal(transport, "auto")) {
8614 sip->auto_transport = TRUE;
8615 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8616 current_service++;
8617 resolve_next_service(sip, current_service);
8618 } else {
8619 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8621 } else if (sipe_strequal(transport, "tls")) {
8622 resolve_next_service(sip, service_tls);
8623 } else if (sipe_strequal(transport, "tcp")) {
8624 resolve_next_service(sip, service_tcp);
8625 } else {
8626 resolve_next_service(sip, service_udp);
8629 g_strfreev(userserver);
8632 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8634 connection_free_all(sip);
8636 g_free(sip->epid);
8637 sip->epid = NULL;
8639 if (sip->query_data != NULL)
8640 purple_dnsquery_destroy(sip->query_data);
8641 sip->query_data = NULL;
8643 if (sip->srv_query_data != NULL)
8644 purple_srv_cancel(sip->srv_query_data);
8645 sip->srv_query_data = NULL;
8647 if (sip->listen_data != NULL)
8648 purple_network_listen_cancel(sip->listen_data);
8649 sip->listen_data = NULL;
8651 if (sip->gsc != NULL)
8652 purple_ssl_close(sip->gsc);
8653 sip->gsc = NULL;
8655 sipe_auth_free(&sip->registrar);
8656 sipe_auth_free(&sip->proxy);
8658 if (sip->txbuf)
8659 purple_circ_buffer_destroy(sip->txbuf);
8660 sip->txbuf = NULL;
8662 g_free(sip->realhostname);
8663 sip->realhostname = NULL;
8665 g_free(sip->server_version);
8666 sip->server_version = NULL;
8668 if (sip->listenpa)
8669 purple_input_remove(sip->listenpa);
8670 sip->listenpa = 0;
8671 if (sip->tx_handler)
8672 purple_input_remove(sip->tx_handler);
8673 sip->tx_handler = 0;
8674 if (sip->resendtimeout)
8675 purple_timeout_remove(sip->resendtimeout);
8676 sip->resendtimeout = 0;
8677 if (sip->timeouts) {
8678 GSList *entry = sip->timeouts;
8679 while (entry) {
8680 struct scheduled_action *sched_action = entry->data;
8681 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8682 purple_timeout_remove(sched_action->timeout_handler);
8683 if (sched_action->destroy) {
8684 (*sched_action->destroy)(sched_action->payload);
8686 g_free(sched_action->name);
8687 g_free(sched_action);
8688 entry = entry->next;
8691 g_slist_free(sip->timeouts);
8693 if (sip->allow_events) {
8694 GSList *entry = sip->allow_events;
8695 while (entry) {
8696 g_free(entry->data);
8697 entry = entry->next;
8700 g_slist_free(sip->allow_events);
8702 if (sip->containers) {
8703 GSList *entry = sip->containers;
8704 while (entry) {
8705 free_container((struct sipe_container *)entry->data);
8706 entry = entry->next;
8709 g_slist_free(sip->containers);
8711 if (sip->contact)
8712 g_free(sip->contact);
8713 sip->contact = NULL;
8714 if (sip->regcallid)
8715 g_free(sip->regcallid);
8716 sip->regcallid = NULL;
8718 if (sip->serveraddr)
8719 g_free(sip->serveraddr);
8720 sip->serveraddr = NULL;
8722 if (sip->focus_factory_uri)
8723 g_free(sip->focus_factory_uri);
8724 sip->focus_factory_uri = NULL;
8726 sip->fd = -1;
8727 sip->processing_input = FALSE;
8729 if (sip->ews) {
8730 sipe_ews_free(sip->ews);
8732 sip->ews = NULL;
8736 * A callback for g_hash_table_foreach_remove
8738 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8739 SIPE_UNUSED_PARAMETER gpointer user_data)
8741 sipe_free_buddy((struct sipe_buddy *) buddy);
8743 /* We must return TRUE as the key/value have already been deleted */
8744 return(TRUE);
8747 static void sipe_close(PurpleConnection *gc)
8749 struct sipe_account_data *sip = gc->proto_data;
8751 if (sip) {
8752 /* leave all conversations */
8753 sipe_session_close_all(sip);
8754 sipe_session_remove_all(sip);
8756 if (sip->csta) {
8757 sip_csta_close(sip);
8760 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8761 /* unsubscribe all */
8762 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8764 /* unregister */
8765 do_register_exp(sip, 0);
8768 sipe_connection_cleanup(sip);
8769 g_free(sip->sipdomain);
8770 g_free(sip->username);
8771 g_free(sip->email);
8772 g_free(sip->password);
8773 g_free(sip->authdomain);
8774 g_free(sip->authuser);
8775 g_free(sip->status);
8776 g_free(sip->note);
8777 g_free(sip->user_states);
8779 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8780 g_hash_table_destroy(sip->buddies);
8781 g_hash_table_destroy(sip->our_publications);
8782 g_hash_table_destroy(sip->user_state_publications);
8783 g_hash_table_destroy(sip->subscriptions);
8784 g_hash_table_destroy(sip->filetransfers);
8786 if (sip->groups) {
8787 GSList *entry = sip->groups;
8788 while (entry) {
8789 struct sipe_group *group = entry->data;
8790 g_free(group->name);
8791 g_free(group);
8792 entry = entry->next;
8795 g_slist_free(sip->groups);
8797 if (sip->our_publication_keys) {
8798 GSList *entry = sip->our_publication_keys;
8799 while (entry) {
8800 g_free(entry->data);
8801 entry = entry->next;
8804 g_slist_free(sip->our_publication_keys);
8806 while (sip->transactions)
8807 transactions_remove(sip, sip->transactions->data);
8809 g_free(gc->proto_data);
8810 gc->proto_data = NULL;
8813 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8814 SIPE_UNUSED_PARAMETER void *user_data)
8816 PurpleAccount *acct = purple_connection_get_account(gc);
8817 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8818 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8819 if (conv == NULL)
8820 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8821 purple_conversation_present(conv);
8822 g_free(id);
8825 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8826 SIPE_UNUSED_PARAMETER void *user_data)
8829 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8830 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8833 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8834 SIPE_UNUSED_PARAMETER struct transaction *trans)
8836 PurpleNotifySearchResults *results;
8837 PurpleNotifySearchColumn *column;
8838 xmlnode *searchResults;
8839 xmlnode *mrow;
8840 int match_count = 0;
8841 gboolean more = FALSE;
8842 gchar *secondary;
8844 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8846 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8847 if (!searchResults) {
8848 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8849 return FALSE;
8852 results = purple_notify_searchresults_new();
8854 if (results == NULL) {
8855 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8856 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8858 xmlnode_free(searchResults);
8859 return FALSE;
8862 column = purple_notify_searchresults_column_new(_("User name"));
8863 purple_notify_searchresults_column_add(results, column);
8865 column = purple_notify_searchresults_column_new(_("Name"));
8866 purple_notify_searchresults_column_add(results, column);
8868 column = purple_notify_searchresults_column_new(_("Company"));
8869 purple_notify_searchresults_column_add(results, column);
8871 column = purple_notify_searchresults_column_new(_("Country"));
8872 purple_notify_searchresults_column_add(results, column);
8874 column = purple_notify_searchresults_column_new(_("Email"));
8875 purple_notify_searchresults_column_add(results, column);
8877 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8878 GList *row = NULL;
8880 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8881 row = g_list_append(row, g_strdup(uri_parts[1]));
8882 g_strfreev(uri_parts);
8884 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8885 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8886 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8887 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8889 purple_notify_searchresults_row_add(results, row);
8890 match_count++;
8893 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8894 char *data = xmlnode_get_data(mrow);
8895 more = (g_strcasecmp(data, "true") == 0);
8896 g_free(data);
8899 secondary = g_strdup_printf(
8900 dngettext(PACKAGE_NAME,
8901 "Found %d contact%s:",
8902 "Found %d contacts%s:", match_count),
8903 match_count, more ? _(" (more matched your query)") : "");
8905 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8906 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8907 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8909 g_free(secondary);
8910 xmlnode_free(searchResults);
8911 return TRUE;
8914 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8916 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8917 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8918 unsigned i = 0;
8920 if (!attrs) return;
8922 do {
8923 PurpleRequestField *field = entries->data;
8924 const char *id = purple_request_field_get_id(field);
8925 const char *value = purple_request_field_string_get_value(field);
8927 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8929 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8930 } while ((entries = g_list_next(entries)) != NULL);
8931 attrs[i] = NULL;
8933 if (i > 0) {
8934 struct sipe_account_data *sip = gc->proto_data;
8935 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8936 gchar *query = g_strjoinv(NULL, attrs);
8937 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8938 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8939 send_soap_request_with_cb(sip, domain_uri, body,
8940 (TransCallback) process_search_contact_response, NULL);
8941 g_free(domain_uri);
8942 g_free(body);
8943 g_free(query);
8946 g_strfreev(attrs);
8949 static void sipe_show_find_contact(PurplePluginAction *action)
8951 PurpleConnection *gc = (PurpleConnection *) action->context;
8952 PurpleRequestFields *fields;
8953 PurpleRequestFieldGroup *group;
8954 PurpleRequestField *field;
8956 fields = purple_request_fields_new();
8957 group = purple_request_field_group_new(NULL);
8958 purple_request_fields_add_group(fields, group);
8960 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8961 purple_request_field_group_add_field(group, field);
8962 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8963 purple_request_field_group_add_field(group, field);
8964 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8965 purple_request_field_group_add_field(group, field);
8966 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8967 purple_request_field_group_add_field(group, field);
8969 purple_request_fields(gc,
8970 _("Search"),
8971 _("Search for a contact"),
8972 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8973 fields,
8974 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8975 _("_Cancel"), NULL,
8976 purple_connection_get_account(gc), NULL, NULL, gc);
8979 static void sipe_show_about_plugin(PurplePluginAction *action)
8981 PurpleConnection *gc = (PurpleConnection *) action->context;
8982 char *tmp = g_strdup_printf(
8984 * Non-translatable parts, like markup, are hard-coded
8985 * into the format string. This requires more translatable
8986 * texts but it makes the translations less error prone.
8988 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
8989 "<br/>"
8990 /* 1 */ "%s:<br/>"
8991 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8992 "<li> - MS Office Communications Server 2007</li><br/>"
8993 "<li> - MS Live Communications Server 2005</li><br/>"
8994 "<li> - MS Live Communications Server 2003</li><br/>"
8995 "<li> - Reuters Messaging</li><br/>"
8996 "<br/>"
8997 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
8998 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8999 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9000 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9001 /* 8 */ "%s: GPLv2+<br/>"
9002 "<br/>"
9003 /* 9 */ "%s:<br/>"
9004 " - CERN<br/>"
9005 " - Reuters Messaging network<br/>"
9006 " - Deutsche Bank<br/>"
9007 " - Merrill Lynch<br/>"
9008 " - Wachovia<br/>"
9009 " - Intel<br/>"
9010 " - Nokia<br/>"
9011 " - HP<br/>"
9012 " - Symantec<br/>"
9013 " - Accenture<br/>"
9014 " - Capgemini<br/>"
9015 " - Siemens<br/>"
9016 " - Alcatel-Lucent<br/>"
9017 " - BT<br/>"
9018 "<br/>"
9019 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9020 "<br/>"
9021 /* 12 */ "<b>%s:</b><br/>"
9022 " - Anibal Avelar<br/>"
9023 " - Gabriel Burt<br/>"
9024 " - Stefan Becker<br/>"
9025 " - pier11<br/>"
9026 " - Jakub Adam<br/>"
9027 " - Tomáš Hrabčík<br/>"
9028 "<br/>"
9029 /* 13 */ "%s<br/>"
9031 /* The next 13 texts make up the SIPE about note text */
9032 /* About note, part 1/13: introduction */
9033 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9034 /* About note, part 2/13: home page URL (label) */
9035 _("Home"),
9036 /* About note, part 3/13: support forum URL (label) */
9037 _("Support"),
9038 /* About note, part 4/13: support forum name (hyperlink text) */
9039 _("Help Forum"),
9040 /* About note, part 5/13: bug tracker URL (label) */
9041 _("Report Problems"),
9042 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9043 _("Bug Tracker"),
9044 /* About note, part 7/13: translation service URL (label) */
9045 _("Translations"),
9046 /* About note, part 8/13: license type (label) */
9047 _("License"),
9048 /* About note, part 9/13: known users */
9049 _("We support users in such organizations as"),
9050 /* About note, part 10/13: translation request, text before Transifex.net URL */
9051 /* append a space if text is not empty */
9052 _("Please help us to translate SIPE to your native language here at "),
9053 /* About note, part 11/13: translation request, text after Transifex.net URL */
9054 /* start with a space if text is not empty */
9055 _(" using convenient web interface"),
9056 /* About note, part 12/13: author list (header) */
9057 _("Authors"),
9058 /* About note, part 13/13: Localization credit */
9059 /* PLEASE NOTE: do *NOT* simply translate the english original */
9060 /* but write something similar to the following sentence: */
9061 /* "Localization for <language name> (<language code>): <name>" */
9062 _("Original texts in English (en): SIPE developers")
9064 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9065 g_free(tmp);
9068 static void sipe_republish_calendar(PurplePluginAction *action)
9070 PurpleConnection *gc = (PurpleConnection *) action->context;
9071 struct sipe_account_data *sip = gc->proto_data;
9073 sipe_update_calendar(sip);
9076 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9077 gpointer value,
9078 GString* str)
9080 struct sipe_publication *publication = value;
9082 g_string_append_printf( str,
9083 SIPE_PUB_XML_PUBLICATION_CLEAR,
9084 publication->category,
9085 publication->instance,
9086 publication->container,
9087 publication->version,
9088 "static");
9091 static void sipe_reset_status(PurplePluginAction *action)
9093 PurpleConnection *gc = (PurpleConnection *) action->context;
9094 struct sipe_account_data *sip = gc->proto_data;
9096 if (sip->ocs2007) /* 2007+ */
9098 GString* str = g_string_new(NULL);
9099 gchar *publications;
9101 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9102 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9103 return;
9106 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9107 publications = g_string_free(str, FALSE);
9109 send_presence_publish(sip, publications);
9110 g_free(publications);
9112 else /* 2005 */
9114 send_presence_soap0(sip, FALSE, TRUE);
9118 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9119 gpointer context)
9121 PurpleConnection *gc = (PurpleConnection *)context;
9122 struct sipe_account_data *sip = gc->proto_data;
9123 GList *menu = NULL;
9124 PurplePluginAction *act;
9125 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9127 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9128 menu = g_list_prepend(menu, act);
9130 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9131 menu = g_list_prepend(menu, act);
9133 if (sipe_strequal(calendar, "EXCH")) {
9134 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9135 menu = g_list_prepend(menu, act);
9138 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9139 menu = g_list_prepend(menu, act);
9141 menu = g_list_reverse(menu);
9143 return menu;
9146 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9150 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9152 return TRUE;
9156 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9158 return TRUE;
9162 static char *sipe_status_text(PurpleBuddy *buddy)
9164 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9165 const PurpleStatus *status = purple_presence_get_active_status(presence);
9166 const char *status_id = purple_status_get_id(status);
9167 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9168 struct sipe_buddy *sbuddy;
9169 char *text = NULL;
9171 if (!sip) return NULL; /* happens on pidgin exit */
9173 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9174 if (sbuddy) {
9175 const char *activity_str = sbuddy->activity ?
9176 sbuddy->activity :
9177 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9178 purple_status_get_name(status) : NULL;
9180 if (activity_str && sbuddy->note)
9182 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9184 else if (activity_str)
9186 text = g_strdup(activity_str);
9188 else if (sbuddy->note)
9190 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9194 return text;
9197 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9199 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9200 const PurpleStatus *status = purple_presence_get_active_status(presence);
9201 struct sipe_account_data *sip;
9202 struct sipe_buddy *sbuddy;
9203 char *note = NULL;
9204 gboolean is_oof_note = FALSE;
9205 char *activity = NULL;
9206 char *calendar = NULL;
9207 char *meeting_subject = NULL;
9208 char *meeting_location = NULL;
9210 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9211 if (sip) //happens on pidgin exit
9213 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9214 if (sbuddy)
9216 note = sbuddy->note;
9217 is_oof_note = sbuddy->is_oof_note;
9218 activity = sbuddy->activity;
9219 calendar = sipe_cal_get_description(sbuddy);
9220 meeting_subject = sbuddy->meeting_subject;
9221 meeting_location = sbuddy->meeting_location;
9225 //Layout
9226 if (purple_presence_is_online(presence))
9228 const char *status_str = activity ? activity : purple_status_get_name(status);
9230 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9232 if (purple_presence_is_online(presence) &&
9233 !is_empty(calendar))
9235 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9237 g_free(calendar);
9238 if (!is_empty(meeting_location))
9240 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9242 if (!is_empty(meeting_subject))
9244 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9247 if (note)
9249 char *tmp = g_strdup_printf("<i>%s</i>", note);
9250 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9252 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9253 g_free(tmp);
9258 #if PURPLE_VERSION_CHECK(2,5,0)
9259 static GHashTable *
9260 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9262 GHashTable *table;
9263 table = g_hash_table_new(g_str_hash, g_str_equal);
9264 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9265 return table;
9267 #endif
9269 static PurpleBuddy *
9270 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9272 PurpleBuddy *clone;
9273 const gchar *server_alias, *email;
9274 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9276 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9278 purple_blist_add_buddy(clone, NULL, group, NULL);
9280 server_alias = purple_buddy_get_server_alias(buddy);
9281 if (server_alias) {
9282 purple_blist_server_alias_buddy(clone, server_alias);
9285 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9286 if (email) {
9287 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9290 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9291 //for UI to update;
9292 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9293 return clone;
9296 static void
9297 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9299 PurpleBuddy *buddy, *b;
9300 PurpleConnection *gc;
9301 PurpleGroup * group = purple_find_group(group_name);
9303 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9305 buddy = (PurpleBuddy *)node;
9307 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9308 gc = purple_account_get_connection(buddy->account);
9310 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9311 if (!b){
9312 purple_blist_add_buddy_clone(group, buddy);
9315 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9318 static void
9319 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9321 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9323 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9325 /* 2007+ conference */
9326 if (sip->ocs2007)
9328 sipe_conf_add(sip, buddy->name);
9330 else /* 2005- multiparty chat */
9332 gchar *self = sip_uri_self(sip);
9333 struct sip_session *session;
9335 session = sipe_session_add_chat(sip);
9336 session->chat_title = sipe_chat_get_name(session->callid);
9337 session->roster_manager = g_strdup(self);
9339 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9340 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9341 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9342 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9344 g_free(self);
9348 static gboolean
9349 sipe_is_election_finished(struct sip_session *session)
9351 gboolean res = TRUE;
9353 SIPE_DIALOG_FOREACH {
9354 if (dialog->election_vote == 0) {
9355 res = FALSE;
9356 break;
9358 } SIPE_DIALOG_FOREACH_END;
9360 if (res) {
9361 session->is_voting_in_progress = FALSE;
9363 return res;
9366 static void
9367 sipe_election_start(struct sipe_account_data *sip,
9368 struct sip_session *session)
9370 int election_timeout;
9372 if (session->is_voting_in_progress) {
9373 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9374 return;
9375 } else {
9376 session->is_voting_in_progress = TRUE;
9378 session->bid = rand();
9380 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9382 SIPE_DIALOG_FOREACH {
9383 /* reset election_vote for each chat participant */
9384 dialog->election_vote = 0;
9386 /* send RequestRM to each chat participant*/
9387 sipe_send_election_request_rm(sip, dialog, session->bid);
9388 } SIPE_DIALOG_FOREACH_END;
9390 election_timeout = 15; /* sec */
9391 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9395 * @param who a URI to whom to invite to chat
9397 void
9398 sipe_invite_to_chat(struct sipe_account_data *sip,
9399 struct sip_session *session,
9400 const gchar *who)
9402 /* a conference */
9403 if (session->focus_uri)
9405 sipe_invite_conf(sip, session, who);
9407 else /* a multi-party chat */
9409 gchar *self = sip_uri_self(sip);
9410 if (session->roster_manager) {
9411 if (sipe_strcase_equal(session->roster_manager, self)) {
9412 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9413 } else {
9414 sipe_refer(sip, session, who);
9416 } else {
9417 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9419 session->pending_invite_queue = slist_insert_unique_sorted(
9420 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9422 sipe_election_start(sip, session);
9424 g_free(self);
9428 void
9429 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9430 struct sip_session *session)
9432 gchar *invitee;
9433 GSList *entry = session->pending_invite_queue;
9435 while (entry) {
9436 invitee = entry->data;
9437 sipe_invite_to_chat(sip, session, invitee);
9438 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9439 g_free(invitee);
9443 static void
9444 sipe_election_result(struct sipe_account_data *sip,
9445 void *sess)
9447 struct sip_session *session = (struct sip_session *)sess;
9448 gchar *rival;
9449 gboolean has_won = TRUE;
9451 if (session->roster_manager) {
9452 purple_debug_info("sipe",
9453 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9454 return;
9457 session->is_voting_in_progress = FALSE;
9459 SIPE_DIALOG_FOREACH {
9460 if (dialog->election_vote < 0) {
9461 has_won = FALSE;
9462 rival = dialog->with;
9463 break;
9465 } SIPE_DIALOG_FOREACH_END;
9467 if (has_won) {
9468 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9470 session->roster_manager = sip_uri_self(sip);
9472 SIPE_DIALOG_FOREACH {
9473 /* send SetRM to each chat participant*/
9474 sipe_send_election_set_rm(sip, dialog);
9475 } SIPE_DIALOG_FOREACH_END;
9476 } else {
9477 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9479 session->bid = 0;
9481 sipe_process_pending_invite_queue(sip, session);
9485 * For 2007+ conference only.
9487 static void
9488 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9490 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9491 struct sip_session *session;
9493 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9494 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9496 session = sipe_session_find_chat_by_title(sip, chat_title);
9498 sipe_conf_modify_user_role(sip, session, buddy->name);
9502 * For 2007+ conference only.
9504 static void
9505 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9507 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9508 struct sip_session *session;
9510 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9511 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9513 session = sipe_session_find_chat_by_title(sip, chat_title);
9515 sipe_conf_delete_user(sip, session, buddy->name);
9518 static void
9519 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9521 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9522 struct sip_session *session;
9524 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9525 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9527 session = sipe_session_find_chat_by_title(sip, chat_title);
9529 sipe_invite_to_chat(sip, session, buddy->name);
9532 static void
9533 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9535 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9537 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9538 if (phone) {
9539 char *tel_uri = sip_to_tel_uri(phone);
9541 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9542 sip_csta_make_call(sip, tel_uri);
9544 g_free(tel_uri);
9548 static void
9549 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9551 const gchar *email;
9552 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9554 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9555 if (email)
9557 char *mailto = g_strdup_printf("mailto:%s", email);
9558 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9559 #ifndef _WIN32
9561 pid_t pid;
9562 char *const parmList[] = {"xdg-email", mailto, NULL};
9563 if ((pid = fork()) == -1)
9565 purple_debug_info("sipe", "fork() error\n");
9567 else if (pid == 0)
9569 execvp(parmList[0], parmList);
9570 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9573 #else
9575 BOOL ret;
9576 _flushall();
9577 errno = 0;
9578 //@TODO resolve env variable %WINDIR% first
9579 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9580 if (errno)
9582 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9585 #endif
9587 g_free(mailto);
9589 else
9591 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9596 * A menu which appear when right-clicking on buddy in contact list.
9598 static GList *
9599 sipe_buddy_menu(PurpleBuddy *buddy)
9601 PurpleBlistNode *g_node;
9602 PurpleGroup *group, *gr_parent;
9603 PurpleMenuAction *act;
9604 GList *menu = NULL;
9605 GList *menu_groups = NULL;
9606 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9607 const char *email;
9608 const char *phone;
9609 const char *phone_disp_str;
9610 gchar *self = sip_uri_self(sip);
9612 SIPE_SESSION_FOREACH {
9613 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9615 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9617 PurpleConvChatBuddyFlags flags;
9618 PurpleConvChatBuddyFlags flags_us;
9620 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9621 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9622 if (session->focus_uri
9623 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9624 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9626 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9627 act = purple_menu_action_new(label,
9628 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9629 session->chat_title, NULL);
9630 g_free(label);
9631 menu = g_list_prepend(menu, act);
9634 if (session->focus_uri
9635 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9637 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9638 act = purple_menu_action_new(label,
9639 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9640 session->chat_title, NULL);
9641 g_free(label);
9642 menu = g_list_prepend(menu, act);
9645 else
9647 if (!session->focus_uri
9648 || (session->focus_uri && !session->locked))
9650 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9651 act = purple_menu_action_new(label,
9652 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9653 session->chat_title, NULL);
9654 g_free(label);
9655 menu = g_list_prepend(menu, act);
9659 } SIPE_SESSION_FOREACH_END;
9661 act = purple_menu_action_new(_("New chat"),
9662 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9663 NULL, NULL);
9664 menu = g_list_prepend(menu, act);
9666 if (sip->csta && !sip->csta->line_status) {
9667 gchar *tmp = NULL;
9668 /* work phone */
9669 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9670 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9671 if (phone) {
9672 gchar *label = g_strdup_printf(_("Work %s"),
9673 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9674 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9675 g_free(tmp);
9676 tmp = NULL;
9677 g_free(label);
9678 menu = g_list_prepend(menu, act);
9681 /* mobile phone */
9682 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9683 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9684 if (phone) {
9685 gchar *label = g_strdup_printf(_("Mobile %s"),
9686 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9687 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9688 g_free(tmp);
9689 tmp = NULL;
9690 g_free(label);
9691 menu = g_list_prepend(menu, act);
9694 /* home phone */
9695 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9696 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9697 if (phone) {
9698 gchar *label = g_strdup_printf(_("Home %s"),
9699 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9700 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9701 g_free(tmp);
9702 tmp = NULL;
9703 g_free(label);
9704 menu = g_list_prepend(menu, act);
9707 /* other phone */
9708 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9709 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9710 if (phone) {
9711 gchar *label = g_strdup_printf(_("Other %s"),
9712 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9713 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9714 g_free(tmp);
9715 tmp = NULL;
9716 g_free(label);
9717 menu = g_list_prepend(menu, act);
9720 /* custom1 phone */
9721 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9722 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9723 if (phone) {
9724 gchar *label = g_strdup_printf(_("Custom1 %s"),
9725 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9726 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9727 g_free(tmp);
9728 tmp = NULL;
9729 g_free(label);
9730 menu = g_list_prepend(menu, act);
9734 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9735 if (email) {
9736 act = purple_menu_action_new(_("Send email..."),
9737 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9738 NULL, NULL);
9739 menu = g_list_prepend(menu, act);
9742 gr_parent = purple_buddy_get_group(buddy);
9743 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9744 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9745 continue;
9747 group = (PurpleGroup *)g_node;
9748 if (group == gr_parent)
9749 continue;
9751 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9752 continue;
9754 act = purple_menu_action_new(purple_group_get_name(group),
9755 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9756 group->name, NULL);
9757 menu_groups = g_list_prepend(menu_groups, act);
9759 menu_groups = g_list_reverse(menu_groups);
9761 act = purple_menu_action_new(_("Copy to"),
9762 NULL,
9763 NULL, menu_groups);
9764 menu = g_list_prepend(menu, act);
9765 menu = g_list_reverse(menu);
9767 g_free(self);
9768 return menu;
9771 static void
9772 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9774 struct sipe_account_data *sip = chat->account->gc->proto_data;
9775 struct sip_session *session;
9777 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9778 sipe_conf_modify_conference_lock(sip, session, locked);
9781 static void
9782 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9784 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9785 sipe_conf_modify_lock(chat, FALSE);
9788 static void
9789 sipe_chat_menu_lock_cb(PurpleChat *chat)
9791 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9792 sipe_conf_modify_lock(chat, TRUE);
9795 static GList *
9796 sipe_chat_menu(PurpleChat *chat)
9798 PurpleMenuAction *act;
9799 PurpleConvChatBuddyFlags flags_us;
9800 GList *menu = NULL;
9801 struct sipe_account_data *sip = chat->account->gc->proto_data;
9802 struct sip_session *session;
9803 gchar *self;
9805 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9806 if (!session) return NULL;
9808 self = sip_uri_self(sip);
9809 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9811 if (session->focus_uri
9812 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9814 if (session->locked) {
9815 act = purple_menu_action_new(_("Unlock"),
9816 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9817 NULL, NULL);
9818 menu = g_list_prepend(menu, act);
9819 } else {
9820 act = purple_menu_action_new(_("Lock"),
9821 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9822 NULL, NULL);
9823 menu = g_list_prepend(menu, act);
9827 menu = g_list_reverse(menu);
9829 g_free(self);
9830 return menu;
9833 static GList *
9834 sipe_blist_node_menu(PurpleBlistNode *node)
9836 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9837 return sipe_buddy_menu((PurpleBuddy *) node);
9838 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9839 return sipe_chat_menu((PurpleChat *)node);
9840 } else {
9841 return NULL;
9845 static gboolean
9846 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9848 char *uri = trans->payload->data;
9850 PurpleNotifyUserInfo *info;
9851 PurpleBuddy *pbuddy = NULL;
9852 struct sipe_buddy *sbuddy;
9853 const char *alias = NULL;
9854 char *device_name = NULL;
9855 char *server_alias = NULL;
9856 char *phone_number = NULL;
9857 char *email = NULL;
9858 const char *site;
9859 char *first_name = NULL;
9860 char *last_name = NULL;
9862 if (!sip) return FALSE;
9864 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9866 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9867 alias = purple_buddy_get_local_alias(pbuddy);
9869 //will query buddy UA's capabilities and send answer to log
9870 sipe_options_request(sip, uri);
9872 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9873 if (sbuddy) {
9874 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9877 info = purple_notify_user_info_new();
9879 if (msg->response != 200) {
9880 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9881 } else {
9882 xmlnode *searchResults;
9883 xmlnode *mrow;
9885 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9886 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9887 if (!searchResults) {
9888 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9889 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9890 const char *value;
9891 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9892 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9893 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9895 /* For 2007 system we will take this from ContactCard -
9896 * it has cleaner tel: URIs at least
9898 if (!sip->ocs2007) {
9899 char *tel_uri = sip_to_tel_uri(phone_number);
9900 /* trims its parameters, so call first */
9901 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9902 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9903 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9904 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9905 g_free(tel_uri);
9908 if (server_alias && strlen(server_alias) > 0) {
9909 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9911 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9912 purple_notify_user_info_add_pair(info, _("Job title"), value);
9914 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9915 purple_notify_user_info_add_pair(info, _("Office"), value);
9917 if (phone_number && strlen(phone_number) > 0) {
9918 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9920 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9921 purple_notify_user_info_add_pair(info, _("Company"), value);
9923 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9924 purple_notify_user_info_add_pair(info, _("City"), value);
9926 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9927 purple_notify_user_info_add_pair(info, _("State"), value);
9929 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9930 purple_notify_user_info_add_pair(info, _("Country"), value);
9932 if (email && strlen(email) > 0) {
9933 purple_notify_user_info_add_pair(info, _("Email address"), email);
9937 xmlnode_free(searchResults);
9940 purple_notify_user_info_add_section_break(info);
9942 if (is_empty(server_alias)) {
9943 g_free(server_alias);
9944 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9945 if (server_alias) {
9946 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9950 /* present alias if it differs from server alias */
9951 if (alias && !sipe_strequal(alias, server_alias))
9953 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9956 if (is_empty(email)) {
9957 g_free(email);
9958 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9959 if (email) {
9960 purple_notify_user_info_add_pair(info, _("Email address"), email);
9964 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9965 if (site) {
9966 purple_notify_user_info_add_pair(info, _("Site"), site);
9969 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9970 if (first_name && last_name) {
9971 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9973 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9974 g_free(link);
9976 g_free(first_name);
9977 g_free(last_name);
9979 if (device_name) {
9980 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9983 /* show a buddy's user info in a nice dialog box */
9984 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9985 uri, /* buddy's URI */
9986 info, /* body */
9987 NULL, /* callback called when dialog closed */
9988 NULL); /* userdata for callback */
9990 g_free(phone_number);
9991 g_free(server_alias);
9992 g_free(email);
9993 g_free(device_name);
9995 return TRUE;
9999 * AD search first, LDAP based
10001 static void sipe_get_info(PurpleConnection *gc, const char *username)
10003 struct sipe_account_data *sip = gc->proto_data;
10004 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10005 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10006 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10007 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10009 payload->destroy = g_free;
10010 payload->data = g_strdup(username);
10012 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
10013 send_soap_request_with_cb(sip, domain_uri, body,
10014 (TransCallback) process_get_info_response, payload);
10015 g_free(domain_uri);
10016 g_free(body);
10017 g_free(row);
10020 PurplePluginProtocolInfo prpl_info =
10022 OPT_PROTO_CHAT_TOPIC,
10023 NULL, /* user_splits */
10024 NULL, /* protocol_options */
10025 NO_BUDDY_ICONS, /* icon_spec */
10026 sipe_list_icon, /* list_icon */
10027 NULL, /* list_emblems */
10028 sipe_status_text, /* status_text */
10029 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10030 sipe_status_types, /* away_states */
10031 sipe_blist_node_menu, /* blist_node_menu */
10032 NULL, /* chat_info */
10033 NULL, /* chat_info_defaults */
10034 sipe_login, /* login */
10035 sipe_close, /* close */
10036 sipe_im_send, /* send_im */
10037 NULL, /* set_info */ // TODO maybe
10038 sipe_send_typing, /* send_typing */
10039 sipe_get_info, /* get_info */
10040 sipe_set_status, /* set_status */
10041 sipe_set_idle, /* set_idle */
10042 NULL, /* change_passwd */
10043 sipe_add_buddy, /* add_buddy */
10044 NULL, /* add_buddies */
10045 sipe_remove_buddy, /* remove_buddy */
10046 NULL, /* remove_buddies */
10047 sipe_add_permit, /* add_permit */
10048 sipe_add_deny, /* add_deny */
10049 sipe_add_deny, /* rem_permit */
10050 sipe_add_permit, /* rem_deny */
10051 dummy_permit_deny, /* set_permit_deny */
10052 NULL, /* join_chat */
10053 NULL, /* reject_chat */
10054 NULL, /* get_chat_name */
10055 sipe_chat_invite, /* chat_invite */
10056 sipe_chat_leave, /* chat_leave */
10057 NULL, /* chat_whisper */
10058 sipe_chat_send, /* chat_send */
10059 sipe_keep_alive, /* keepalive */
10060 NULL, /* register_user */
10061 NULL, /* get_cb_info */ // deprecated
10062 NULL, /* get_cb_away */ // deprecated
10063 sipe_alias_buddy, /* alias_buddy */
10064 sipe_group_buddy, /* group_buddy */
10065 sipe_rename_group, /* rename_group */
10066 NULL, /* buddy_free */
10067 sipe_convo_closed, /* convo_closed */
10068 purple_normalize_nocase, /* normalize */
10069 NULL, /* set_buddy_icon */
10070 sipe_remove_group, /* remove_group */
10071 NULL, /* get_cb_real_name */ // TODO?
10072 NULL, /* set_chat_topic */
10073 NULL, /* find_blist_chat */
10074 NULL, /* roomlist_get_list */
10075 NULL, /* roomlist_cancel */
10076 NULL, /* roomlist_expand_category */
10077 NULL, /* can_receive_file */
10078 sipe_ft_send_file, /* send_file */
10079 sipe_ft_new_xfer, /* new_xfer */
10080 NULL, /* offline_message */
10081 NULL, /* whiteboard_prpl_ops */
10082 sipe_send_raw, /* send_raw */
10083 NULL, /* roomlist_room_serialize */
10084 NULL, /* unregister_user */
10085 NULL, /* send_attention */
10086 NULL, /* get_attention_types */
10087 #if !PURPLE_VERSION_CHECK(2,5,0)
10088 /* Backward compatibility when compiling against 2.4.x API */
10089 (void (*)(void)) /* _purple_reserved4 */
10090 #endif
10091 sizeof(PurplePluginProtocolInfo), /* struct_size */
10092 #if PURPLE_VERSION_CHECK(2,5,0)
10093 sipe_get_account_text_table, /* get_account_text_table */
10094 #if PURPLE_VERSION_CHECK(2,6,0)
10095 NULL, /* initiate_media */
10096 NULL, /* get_media_caps */
10097 #endif
10098 #endif
10102 PurplePluginInfo info = {
10103 PURPLE_PLUGIN_MAGIC,
10104 PURPLE_MAJOR_VERSION,
10105 PURPLE_MINOR_VERSION,
10106 PURPLE_PLUGIN_PROTOCOL, /**< type */
10107 NULL, /**< ui_requirement */
10108 0, /**< flags */
10109 NULL, /**< dependencies */
10110 PURPLE_PRIORITY_DEFAULT, /**< priority */
10111 "prpl-sipe", /**< id */
10112 "Office Communicator", /**< name */
10113 PACKAGE_VERSION, /**< version */
10114 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10115 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10116 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10117 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10118 "Gabriel Burt <gburt@novell.com>, " /**< author */
10119 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10120 "pier11 <pier11@operamail.com>", /**< author */
10121 PACKAGE_URL, /**< homepage */
10122 sipe_plugin_load, /**< load */
10123 sipe_plugin_unload, /**< unload */
10124 sipe_plugin_destroy, /**< destroy */
10125 NULL, /**< ui_info */
10126 &prpl_info, /**< extra_info */
10127 NULL,
10128 sipe_actions,
10129 NULL,
10130 NULL,
10131 NULL,
10132 NULL
10135 void sipe_core_init(void)
10137 srand(time(NULL));
10138 sip_sec_init();
10140 #ifdef ENABLE_NLS
10141 SIPE_DEBUG_INFO("bindtextdomain = %s",
10142 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10143 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10144 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10145 textdomain(PACKAGE_NAME);
10146 #endif
10149 void sipe_core_destroy(void)
10151 sip_sec_destroy();
10155 Local Variables:
10156 mode: c
10157 c-file-style: "bsd"
10158 indent-tabs-mode: t
10159 tab-width: 8
10160 End: