presence: fix endless publish/409 error loop at startup
[siplcs.git] / src / core / sipe.c
blob3413ff132a610090fe3d64d1cfba15fc9cbcaa87
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 "dnsquery.h"
74 #include "dnssrv.h"
75 #include "ft.h"
76 #include "network.h"
77 #include "notify.h"
78 #include "plugin.h"
79 #include "privacy.h"
80 #include "request.h"
81 #include "savedstatuses.h"
82 #include "sslconn.h"
83 #include "version.h"
84 #include "xmlnode.h"
86 #include "core-depurple.h" /* Temporary for the core de-purple transition */
88 #include "sipmsg.h"
89 #include "sip-csta.h"
90 #include "sip-sec.h"
91 #include "sipe-backend.h"
92 #include "sipe-cal.h"
93 #include "sipe-chat.h"
94 #include "sipe-conf.h"
95 #include "sipe-core.h"
96 #include "sipe-dialog.h"
97 #include "sipe-ews.h"
98 #include "sipe-ft.h"
99 #include "sipe-mime.h"
100 #include "sipe-nls.h"
101 #include "sipe-session.h"
102 #include "sipe-sign.h"
103 #include "sipe-utils.h"
104 #include "sipe-xml.h"
105 #include "http-conn.h"
106 #include "uuid.h"
107 #include "sipe.h"
109 /* Backward compatibility when compiling against 2.4.x API */
110 #if !PURPLE_VERSION_CHECK(2,5,0)
111 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
112 #endif
114 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
116 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
117 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
119 /* Keep in sync with sipe_transport_type! */
120 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
121 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
123 /* Status identifiers (see also: sipe_status_types()) */
124 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
125 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
126 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
127 /* PURPLE_STATUS_UNAVAILABLE: */
128 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
129 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
130 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
131 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
132 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
133 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
134 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
135 /* PURPLE_STATUS_AWAY: */
136 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
137 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
138 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
139 /** Reuters status (user settable) */
140 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
141 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
142 /* ??? PURPLE_STATUS_MOBILE */
143 /* ??? PURPLE_STATUS_TUNE */
145 /* Status attributes (see also sipe_status_types() */
146 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
148 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
150 static struct sipe_activity_map_struct
152 sipe_activity type;
153 const char *token;
154 const char *desc;
155 const char *status_id;
157 } const sipe_activity_map[] =
159 /* This has nothing to do with Availability numbers, like 3500 (online).
160 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
162 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
163 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
164 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
165 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
166 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
167 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
168 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
169 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
170 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
171 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
172 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
173 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
174 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
175 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
176 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
178 /** @param x is sipe_activity */
179 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
182 /* Action name templates */
183 #define ACTION_NAME_PRESENCE "<presence><%s>"
185 static sipe_activity
186 sipe_get_activity_by_token(const char *token)
188 int i;
190 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
192 if (sipe_strequal(token, sipe_activity_map[i].token))
193 return sipe_activity_map[i].type;
196 return sipe_activity_map[0].type;
199 static const char *
200 sipe_get_activity_desc_by_token(const char *token)
202 if (!token) return NULL;
204 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
207 /** Allows to send typed messages from chat window again after account reinstantiation. */
208 static void
209 sipe_rejoin_chat(PurpleConversation *conv)
211 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
212 PURPLE_CONV_CHAT(conv)->left)
214 PURPLE_CONV_CHAT(conv)->left = FALSE;
215 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
219 static char *genbranch()
221 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
222 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
223 rand() & 0xFFFF, rand() & 0xFFFF);
227 static char *default_ua = NULL;
228 static const char*
229 sipe_get_useragent(struct sipe_account_data *sip)
231 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
232 if (is_empty(useragent)) {
233 if (!default_ua) {
234 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
235 /* ref: lzodefs.h */
236 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
237 #define SIPE_TARGET_PLATFORM "linux"
238 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
239 #define SIPE_TARGET_PLATFORM "bsd"
240 #elif defined(__APPLE__) || defined(__MACOS__)
241 #define SIPE_TARGET_PLATFORM "macosx"
242 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
243 #define SIPE_TARGET_PLATFORM "aix"
244 #elif defined(__solaris__) || defined(__sun)
245 #define SIPE_TARGET_PLATFORM "sun"
246 #elif defined(_WIN32)
247 #define SIPE_TARGET_PLATFORM "win"
248 #elif defined(__CYGWIN__)
249 #define SIPE_TARGET_PLATFORM "cygwin"
250 #elif defined(__hpux__)
251 #define SIPE_TARGET_PLATFORM "hpux"
252 #elif defined(__sgi__)
253 #define SIPE_TARGET_PLATFORM "irix"
254 #else
255 #define SIPE_TARGET_PLATFORM "unknown"
256 #endif
258 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
259 #define SIPE_TARGET_ARCH "x86_64"
260 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
261 #define SIPE_TARGET_ARCH "i386"
262 #elif defined(__ppc64__)
263 #define SIPE_TARGET_ARCH "ppc64"
264 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
265 #define SIPE_TARGET_ARCH "ppc"
266 #elif defined(__hppa__) || defined(__hppa)
267 #define SIPE_TARGET_ARCH "hppa"
268 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
269 #define SIPE_TARGET_ARCH "mips"
270 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
271 #define SIPE_TARGET_ARCH "s390"
272 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
273 #define SIPE_TARGET_ARCH "sparc"
274 #elif defined(__arm__)
275 #define SIPE_TARGET_ARCH "arm"
276 #else
277 #define SIPE_TARGET_ARCH "other"
278 #endif
280 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
281 purple_core_get_version(),
282 sip->server_version ? sip->server_version : "");
284 useragent = default_ua;
286 return useragent;
289 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
290 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
292 return "sipe";
295 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
297 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
298 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
299 gpointer data);
301 static void sipe_close(PurpleConnection *gc);
303 static void send_presence_status(struct sipe_account_data *sip);
305 static void sendout_pkt(PurpleConnection *gc, const char *buf);
307 static void sipe_keep_alive(PurpleConnection *gc)
309 struct sipe_account_data *sip = gc->proto_data;
310 if (sip->transport == SIPE_TRANSPORT_UDP) {
311 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
312 gchar buf[2] = {0, 0};
313 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
314 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
315 } else {
316 time_t now = time(NULL);
317 if ((sip->keepalive_timeout > 0) &&
318 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
319 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
321 SIPE_DEBUG_INFO("sending keep alive %d", sip->keepalive_timeout);
322 sendout_pkt(gc, "\r\n\r\n");
323 sip->last_keepalive = now;
328 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
330 struct sip_connection *ret = NULL;
331 GSList *entry = sip->openconns;
332 while (entry) {
333 ret = entry->data;
334 if (ret->fd == fd) return ret;
335 entry = entry->next;
337 return NULL;
340 static void sipe_auth_free(struct sip_auth *auth)
342 g_free(auth->opaque);
343 auth->opaque = NULL;
344 g_free(auth->realm);
345 auth->realm = NULL;
346 g_free(auth->target);
347 auth->target = NULL;
348 auth->version = 0;
349 auth->type = AUTH_TYPE_UNSET;
350 auth->retries = 0;
351 auth->expires = 0;
352 g_free(auth->gssapi_data);
353 auth->gssapi_data = NULL;
354 sip_sec_destroy_context(auth->gssapi_context);
355 auth->gssapi_context = NULL;
358 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
360 struct sip_connection *ret = g_new0(struct sip_connection, 1);
361 ret->fd = fd;
362 sip->openconns = g_slist_append(sip->openconns, ret);
363 return ret;
366 static void connection_remove(struct sipe_account_data *sip, int fd)
368 struct sip_connection *conn = connection_find(sip, fd);
369 if (conn) {
370 sip->openconns = g_slist_remove(sip->openconns, conn);
371 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
372 g_free(conn->inbuf);
373 g_free(conn);
377 static void connection_free_all(struct sipe_account_data *sip)
379 struct sip_connection *ret = NULL;
380 GSList *entry = sip->openconns;
381 while (entry) {
382 ret = entry->data;
383 connection_remove(sip, ret->fd);
384 entry = sip->openconns;
388 static void
389 sipe_make_signature(struct sipe_account_data *sip,
390 struct sipmsg *msg);
392 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
394 gchar noncecount[9];
395 const char *authuser = sip->authuser;
396 gchar *response;
397 gchar *ret;
399 if (!authuser || strlen(authuser) < 1) {
400 authuser = sip->username;
403 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
404 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
405 gchar *version_str;
407 // If we have a signature for the message, include that
408 if (msg->signature) {
409 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);
412 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
413 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
414 gchar *gssapi_data;
415 gchar *opaque;
416 gchar *sign_str = NULL;
418 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
419 &(auth->expires),
420 auth->type,
421 purple_account_get_bool(sip->account, "sso", TRUE),
422 sip->authdomain ? sip->authdomain : "",
423 authuser,
424 sip->password,
425 auth->target,
426 auth->gssapi_data);
427 if (!gssapi_data || !auth->gssapi_context) {
428 sip->gc->wants_to_die = TRUE;
429 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
430 return NULL;
433 if (auth->version > 3) {
434 sipe_make_signature(sip, msg);
435 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
436 msg->rand, msg->num, msg->signature);
437 } else {
438 sign_str = g_strdup("");
441 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
442 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
443 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);
444 g_free(opaque);
445 g_free(gssapi_data);
446 g_free(version_str);
447 g_free(sign_str);
448 return ret;
451 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
452 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
453 g_free(version_str);
454 return ret;
456 } else { /* Digest */
458 /* Calculate new session key */
459 if (!auth->opaque) {
460 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
461 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
462 authuser, auth->realm, sip->password,
463 auth->gssapi_data, NULL);
466 sprintf(noncecount, "%08d", auth->nc++);
467 response = purple_cipher_http_digest_calculate_response("md5",
468 msg->method, msg->target, NULL, NULL,
469 auth->gssapi_data, noncecount, NULL,
470 auth->opaque);
471 SIPE_DEBUG_INFO("Digest response %s", response);
473 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);
474 g_free(response);
475 return ret;
479 static char *parse_attribute(const char *attrname, const char *source)
481 const char *tmp, *tmp2;
482 char *retval = NULL;
483 int len = strlen(attrname);
485 if (g_str_has_prefix(source, attrname)) {
486 tmp = source + len;
487 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
488 if (tmp2)
489 retval = g_strndup(tmp, tmp2 - tmp);
490 else
491 retval = g_strdup(tmp);
494 return retval;
497 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
499 int i;
500 gchar **parts;
502 if (!hdr) {
503 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
504 return;
507 if (!g_strncasecmp(hdr, "NTLM", 4)) {
508 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
509 auth->type = AUTH_TYPE_NTLM;
510 hdr += 5;
511 auth->nc = 1;
512 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
513 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
514 auth->type = AUTH_TYPE_KERBEROS;
515 hdr += 9;
516 auth->nc = 3;
517 } else {
518 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
519 auth->type = AUTH_TYPE_DIGEST;
520 hdr += 7;
523 parts = g_strsplit(hdr, "\", ", 0);
524 for (i = 0; parts[i]; i++) {
525 char *tmp;
527 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
529 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
530 g_free(auth->gssapi_data);
531 auth->gssapi_data = tmp;
533 if (auth->type == AUTH_TYPE_NTLM) {
534 /* NTLM module extracts nonce from gssapi-data */
535 auth->nc = 3;
538 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
539 /* Only used with AUTH_TYPE_DIGEST */
540 g_free(auth->gssapi_data);
541 auth->gssapi_data = tmp;
542 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
543 g_free(auth->opaque);
544 auth->opaque = tmp;
545 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
546 g_free(auth->realm);
547 auth->realm = tmp;
549 if (auth->type == AUTH_TYPE_DIGEST) {
550 /* Throw away old session key */
551 g_free(auth->opaque);
552 auth->opaque = NULL;
553 auth->nc = 1;
555 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
556 g_free(auth->target);
557 auth->target = tmp;
558 } else if ((tmp = parse_attribute("version=", parts[i]))) {
559 auth->version = atoi(tmp);
560 g_free(tmp);
562 // uncomment to revert to previous functionality if version 3+ does not work.
563 // auth->version = 2;
565 g_strfreev(parts);
567 return;
570 static void sipe_canwrite_cb(gpointer data,
571 SIPE_UNUSED_PARAMETER gint source,
572 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
574 PurpleConnection *gc = data;
575 struct sipe_account_data *sip = gc->proto_data;
576 gsize max_write;
577 gssize written;
579 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
581 if (max_write == 0) {
582 if (sip->tx_handler != 0){
583 purple_input_remove(sip->tx_handler);
584 sip->tx_handler = 0;
586 return;
589 written = write(sip->fd, sip->txbuf->outptr, max_write);
591 if (written < 0 && errno == EAGAIN)
592 written = 0;
593 else if (written <= 0) {
594 /*TODO: do we really want to disconnect on a failure to write?*/
595 purple_connection_error(gc, _("Could not write"));
596 return;
599 purple_circ_buffer_mark_read(sip->txbuf, written);
602 static void sipe_canwrite_cb_ssl(gpointer data,
603 SIPE_UNUSED_PARAMETER gint src,
604 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
606 PurpleConnection *gc = data;
607 struct sipe_account_data *sip = gc->proto_data;
608 gsize max_write;
609 gssize written;
611 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
613 if (max_write == 0) {
614 if (sip->tx_handler != 0) {
615 purple_input_remove(sip->tx_handler);
616 sip->tx_handler = 0;
617 return;
621 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
623 if (written < 0 && errno == EAGAIN)
624 written = 0;
625 else if (written <= 0) {
626 /*TODO: do we really want to disconnect on a failure to write?*/
627 purple_connection_error(gc, _("Could not write"));
628 return;
631 purple_circ_buffer_mark_read(sip->txbuf, written);
634 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
636 static void send_later_cb(gpointer data, gint source,
637 SIPE_UNUSED_PARAMETER const gchar *error)
639 PurpleConnection *gc = data;
640 struct sipe_account_data *sip;
641 struct sip_connection *conn;
643 if (!PURPLE_CONNECTION_IS_VALID(gc))
645 if (source >= 0)
646 close(source);
647 return;
650 if (source < 0) {
651 purple_connection_error(gc, _("Could not connect"));
652 return;
655 sip = gc->proto_data;
656 sip->fd = source;
657 sip->connecting = FALSE;
658 sip->last_keepalive = time(NULL);
660 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
662 /* If there is more to write now, we need to register a handler */
663 if (sip->txbuf->bufused > 0)
664 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
666 conn = connection_create(sip, source);
667 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
670 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
672 struct sipe_account_data *sip;
674 if (!PURPLE_CONNECTION_IS_VALID(gc))
676 if (gsc) purple_ssl_close(gsc);
677 return NULL;
680 sip = gc->proto_data;
681 sip->fd = gsc->fd;
682 sip->gsc = gsc;
683 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
684 sip->connecting = FALSE;
685 sip->last_keepalive = time(NULL);
687 connection_create(sip, gsc->fd);
689 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
691 return sip;
694 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
695 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
697 PurpleConnection *gc = data;
698 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
699 if (sip == NULL) return;
701 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
703 /* If there is more to write now */
704 if (sip->txbuf->bufused > 0) {
705 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
710 static void sendlater(PurpleConnection *gc, const char *buf)
712 struct sipe_account_data *sip = gc->proto_data;
714 if (!sip->connecting) {
715 SIPE_DEBUG_INFO("connecting to %s port %d", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
716 if (sip->transport == SIPE_TRANSPORT_TLS){
717 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
718 } else {
719 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
720 purple_connection_error(gc, _("Could not create socket"));
723 sip->connecting = TRUE;
726 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
727 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
729 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
732 static void sendout_pkt(PurpleConnection *gc, const char *buf)
734 struct sipe_account_data *sip = gc->proto_data;
735 time_t currtime = time(NULL);
736 int writelen = strlen(buf);
737 char *tmp;
739 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime), tmp = fix_newlines(buf));
740 g_free(tmp);
741 if (sip->transport == SIPE_TRANSPORT_UDP) {
742 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
743 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
745 } else {
746 int ret;
747 if (sip->fd < 0) {
748 sendlater(gc, buf);
749 return;
752 if (sip->tx_handler) {
753 ret = -1;
754 errno = EAGAIN;
755 } else{
756 if (sip->gsc){
757 ret = purple_ssl_write(sip->gsc, buf, writelen);
758 }else{
759 ret = write(sip->fd, buf, writelen);
763 if (ret < 0 && errno == EAGAIN)
764 ret = 0;
765 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
766 sendlater(gc, buf);
767 return;
770 if (ret < writelen) {
771 if (!sip->tx_handler){
772 if (sip->gsc){
773 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
775 else{
776 sip->tx_handler = purple_input_add(sip->fd,
777 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
778 gc);
782 /* XXX: is it OK to do this? You might get part of a request sent
783 with part of another. */
784 if (sip->txbuf->bufused > 0)
785 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
787 purple_circ_buffer_append(sip->txbuf, buf + ret,
788 writelen - ret);
793 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
795 sendout_pkt(gc, buf);
796 return len;
799 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
801 GSList *tmp = msg->headers;
802 gchar *name;
803 gchar *value;
804 GString *outstr = g_string_new("");
805 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
806 while (tmp) {
807 name = ((struct sipnameval*) (tmp->data))->name;
808 value = ((struct sipnameval*) (tmp->data))->value;
809 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
810 tmp = g_slist_next(tmp);
812 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
813 sendout_pkt(sip->gc, outstr->str);
814 g_string_free(outstr, TRUE);
817 static void
818 sipe_make_signature(struct sipe_account_data *sip,
819 struct sipmsg *msg)
821 if (sip->registrar.gssapi_context) {
822 struct sipmsg_breakdown msgbd;
823 gchar *signature_input_str;
824 msgbd.msg = msg;
825 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
826 msgbd.rand = g_strdup_printf("%08x", g_random_int());
827 sip->registrar.ntlm_num++;
828 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
829 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
830 if (signature_input_str != NULL) {
831 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
832 msg->signature = signature_hex;
833 msg->rand = g_strdup(msgbd.rand);
834 msg->num = g_strdup(msgbd.num);
835 g_free(signature_input_str);
837 sipmsg_breakdown_free(&msgbd);
841 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
843 gchar * buf;
845 if (sip->registrar.type == AUTH_TYPE_UNSET) {
846 return;
849 sipe_make_signature(sip, msg);
851 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
852 buf = auth_header(sip, &sip->registrar, msg);
853 if (buf) {
854 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
856 g_free(buf);
857 } 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")) {
858 sip->registrar.nc = 3;
859 sip->registrar.type = AUTH_TYPE_NTLM;
860 #ifdef HAVE_KERBEROS
861 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
862 sip->registrar.type = AUTH_TYPE_KERBEROS;
864 #endif
867 buf = auth_header(sip, &sip->registrar, msg);
868 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
869 g_free(buf);
870 } else {
871 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
875 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
876 const char *text, const char *body)
878 gchar *name;
879 gchar *value;
880 GString *outstr = g_string_new("");
881 struct sipe_account_data *sip = gc->proto_data;
882 gchar *contact;
883 GSList *tmp;
884 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
886 /* Can return NULL! */
887 contact = get_contact(sip);
888 if (contact) {
889 sipmsg_add_header(msg, "Contact", contact);
890 g_free(contact);
893 if (body) {
894 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
895 sipmsg_add_header(msg, "Content-Length", len);
896 g_free(len);
897 } else {
898 sipmsg_add_header(msg, "Content-Length", "0");
901 msg->response = code;
903 sipmsg_strip_headers(msg, keepers);
904 sipmsg_merge_new_headers(msg);
905 sign_outgoing_message(msg, sip, msg->method);
907 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
908 tmp = msg->headers;
909 while (tmp) {
910 name = ((struct sipnameval*) (tmp->data))->name;
911 value = ((struct sipnameval*) (tmp->data))->value;
913 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
914 tmp = g_slist_next(tmp);
916 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
917 sendout_pkt(gc, outstr->str);
918 g_string_free(outstr, TRUE);
921 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
923 if (sip->transactions) {
924 sip->transactions = g_slist_remove(sip->transactions, trans);
925 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
927 if (trans->msg) sipmsg_free(trans->msg);
928 if (trans->payload) {
929 (*trans->payload->destroy)(trans->payload->data);
930 g_free(trans->payload);
932 g_free(trans->key);
933 g_free(trans);
937 static struct transaction *
938 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
940 const gchar *call_id;
941 const gchar *cseq;
942 struct transaction *trans = g_new0(struct transaction, 1);
944 trans->time = time(NULL);
945 trans->msg = (struct sipmsg *)msg;
946 call_id = sipmsg_find_header(trans->msg, "Call-ID");
947 cseq = sipmsg_find_header(trans->msg, "CSeq");
948 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
949 trans->callback = callback;
950 sip->transactions = g_slist_append(sip->transactions, trans);
951 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
952 return trans;
955 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
957 struct transaction *trans;
958 GSList *transactions = sip->transactions;
959 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
960 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
961 gchar *key;
963 if (!call_id || !cseq) {
964 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
965 return NULL;
968 key = g_strdup_printf("<%s><%s>", call_id, cseq);
969 while (transactions) {
970 trans = transactions->data;
971 if (!g_strcasecmp(trans->key, key)) {
972 g_free(key);
973 return trans;
975 transactions = transactions->next;
978 g_free(key);
979 return NULL;
982 struct transaction *
983 send_sip_request(PurpleConnection *gc, const gchar *method,
984 const gchar *url, const gchar *to, const gchar *addheaders,
985 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
987 struct sipe_account_data *sip = gc->proto_data;
988 const char *addh = "";
989 char *buf;
990 struct sipmsg *msg;
991 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
992 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
993 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
994 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
995 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
996 gchar *route = g_strdup("");
997 gchar *epid = get_epid(sip);
998 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
999 struct transaction *trans = NULL;
1001 if (dialog && dialog->routes)
1003 GSList *iter = dialog->routes;
1005 while(iter)
1007 char *tmp = route;
1008 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1009 g_free(tmp);
1010 iter = g_slist_next(iter);
1014 if (!ourtag && !dialog) {
1015 ourtag = gentag();
1018 if (sipe_strequal(method, "REGISTER")) {
1019 if (sip->regcallid) {
1020 g_free(callid);
1021 callid = g_strdup(sip->regcallid);
1022 } else {
1023 sip->regcallid = g_strdup(callid);
1025 cseq = ++sip->cseq;
1028 if (addheaders) addh = addheaders;
1030 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1031 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1032 "From: <sip:%s>%s%s;epid=%s\r\n"
1033 "To: <%s>%s%s%s%s\r\n"
1034 "Max-Forwards: 70\r\n"
1035 "CSeq: %d %s\r\n"
1036 "User-Agent: %s\r\n"
1037 "Call-ID: %s\r\n"
1038 "%s%s"
1039 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1040 method,
1041 dialog && dialog->request ? dialog->request : url,
1042 TRANSPORT_DESCRIPTOR,
1043 purple_network_get_my_ip(-1),
1044 sip->listenport,
1045 branch ? ";branch=" : "",
1046 branch ? branch : "",
1047 sip->username,
1048 ourtag ? ";tag=" : "",
1049 ourtag ? ourtag : "",
1050 epid,
1052 theirtag ? ";tag=" : "",
1053 theirtag ? theirtag : "",
1054 theirepid ? ";epid=" : "",
1055 theirepid ? theirepid : "",
1056 cseq,
1057 method,
1058 sipe_get_useragent(sip),
1059 callid,
1060 route,
1061 addh,
1062 body ? (gsize) strlen(body) : 0,
1063 body ? body : "");
1066 //printf ("parsing msg buf:\n%s\n\n", buf);
1067 msg = sipmsg_parse_msg(buf);
1069 g_free(buf);
1070 g_free(ourtag);
1071 g_free(theirtag);
1072 g_free(theirepid);
1073 g_free(branch);
1074 g_free(callid);
1075 g_free(route);
1076 g_free(epid);
1078 sign_outgoing_message (msg, sip, method);
1080 buf = sipmsg_to_string (msg);
1082 /* add to ongoing transactions */
1083 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1084 if (!sipe_strequal(method, "ACK")) {
1085 trans = transactions_add_buf(sip, msg, tc);
1086 } else {
1087 sipmsg_free(msg);
1089 sendout_pkt(gc, buf);
1090 g_free(buf);
1092 return trans;
1096 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1098 static void
1099 send_soap_request_with_cb(struct sipe_account_data *sip,
1100 gchar *from0,
1101 gchar *body,
1102 TransCallback callback,
1103 struct transaction_payload *payload)
1105 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1106 gchar *contact = get_contact(sip);
1107 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1108 "Content-Type: application/SOAP+xml\r\n",contact);
1110 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1111 trans->payload = payload;
1113 g_free(from);
1114 g_free(contact);
1115 g_free(hdr);
1118 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1120 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1123 static char *get_contact_register(struct sipe_account_data *sip)
1125 char *epid = get_epid(sip);
1126 char *uuid = generateUUIDfromEPID(epid);
1127 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);
1128 g_free(uuid);
1129 g_free(epid);
1130 return(buf);
1133 static void do_register_exp(struct sipe_account_data *sip, int expire)
1135 char *uri;
1136 char *expires;
1137 char *to;
1138 char *contact;
1139 char *hdr;
1141 if (!sip->sipdomain) return;
1143 uri = sip_uri_from_name(sip->sipdomain);
1144 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1145 to = sip_uri_self(sip);
1146 contact = get_contact_register(sip);
1147 hdr = g_strdup_printf("Contact: %s\r\n"
1148 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1149 "Event: registration\r\n"
1150 "Allow-Events: presence\r\n"
1151 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1152 "%s", contact, expires);
1153 g_free(contact);
1154 g_free(expires);
1156 sip->registerstatus = 1;
1158 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1159 process_register_response);
1161 g_free(hdr);
1162 g_free(uri);
1163 g_free(to);
1166 static void do_register_cb(struct sipe_account_data *sip,
1167 SIPE_UNUSED_PARAMETER void *unused)
1169 do_register_exp(sip, -1);
1170 sip->reregister_set = FALSE;
1173 static void do_register(struct sipe_account_data *sip)
1175 do_register_exp(sip, -1);
1178 static void
1179 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1181 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1182 send_soap_request(sip, body);
1183 g_free(body);
1186 static void
1187 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1189 if (allow) {
1190 SIPE_DEBUG_INFO("Authorizing contact %s", who);
1191 } else {
1192 SIPE_DEBUG_INFO("Blocking contact %s", who);
1195 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1198 static
1199 void sipe_auth_user_cb(void * data)
1201 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1202 if (!job) return;
1204 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1205 g_free(job);
1208 static
1209 void sipe_deny_user_cb(void * data)
1211 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1212 if (!job) return;
1214 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1215 g_free(job);
1218 static void
1219 sipe_add_permit(PurpleConnection *gc, const char *name)
1221 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1222 sipe_contact_allow_deny(sip, name, TRUE);
1225 static void
1226 sipe_add_deny(PurpleConnection *gc, const char *name)
1228 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1229 sipe_contact_allow_deny(sip, name, FALSE);
1232 /*static void
1233 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1235 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1236 sipe_contact_set_acl(sip, name, "");
1239 static void
1240 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1242 xmlnode *watchers;
1243 xmlnode *watcher;
1244 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1245 if (msg->response != 0 && msg->response != 200) return;
1247 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1249 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1250 if (!watchers) return;
1252 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1253 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1254 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1255 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1257 // TODO pull out optional displayName to pass as alias
1258 if (remote_user) {
1259 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1260 job->who = remote_user;
1261 job->sip = sip;
1262 purple_account_request_authorization(
1263 sip->account,
1264 remote_user,
1265 _("you"), /* id */
1266 alias,
1267 NULL, /* message */
1268 on_list,
1269 sipe_auth_user_cb,
1270 sipe_deny_user_cb,
1271 (void *) job);
1276 xmlnode_free(watchers);
1277 return;
1280 static void
1281 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1283 PurpleGroup * purple_group = purple_find_group(group->name);
1284 if (!purple_group) {
1285 purple_group = purple_group_new(group->name);
1286 purple_blist_add_group(purple_group, NULL);
1289 if (purple_group) {
1290 group->purple_group = purple_group;
1291 sip->groups = g_slist_append(sip->groups, group);
1292 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1293 } else {
1294 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1298 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1300 struct sipe_group *group;
1301 GSList *entry;
1302 if (sip == NULL) {
1303 return NULL;
1306 entry = sip->groups;
1307 while (entry) {
1308 group = entry->data;
1309 if (group->id == id) {
1310 return group;
1312 entry = entry->next;
1314 return NULL;
1317 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1319 struct sipe_group *group;
1320 GSList *entry;
1321 if (!sip || !name) {
1322 return NULL;
1325 entry = sip->groups;
1326 while (entry) {
1327 group = entry->data;
1328 if (sipe_strequal(group->name, name)) {
1329 return group;
1331 entry = entry->next;
1333 return NULL;
1336 static void
1337 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1339 gchar *body;
1340 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1341 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1342 send_soap_request(sip, body);
1343 g_free(body);
1344 g_free(group->name);
1345 group->name = g_strdup(name);
1349 * Only appends if no such value already stored.
1350 * Like Set in Java.
1352 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1353 GSList * res = list;
1354 if (!g_slist_find_custom(list, data, func)) {
1355 res = g_slist_insert_sorted(list, data, func);
1357 return res;
1360 static int
1361 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1362 return group1->id - group2->id;
1366 * Returns string like "2 4 7 8" - group ids buddy belong to.
1368 static gchar *
1369 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1370 int i = 0;
1371 gchar *res;
1372 //creating array from GList, converting int to gchar*
1373 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1374 GSList *entry = buddy->groups;
1376 if (!ids_arr) return NULL;
1378 while (entry) {
1379 struct sipe_group * group = entry->data;
1380 ids_arr[i] = g_strdup_printf("%d", group->id);
1381 entry = entry->next;
1382 i++;
1384 ids_arr[i] = NULL;
1385 res = g_strjoinv(" ", ids_arr);
1386 g_strfreev(ids_arr);
1387 return res;
1391 * Sends buddy update to server
1393 static void
1394 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1396 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1397 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1399 if (buddy && purple_buddy) {
1400 const char *alias = purple_buddy_get_alias(purple_buddy);
1401 gchar *groups = sipe_get_buddy_groups_string(buddy);
1402 if (groups) {
1403 gchar *body;
1404 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1406 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1407 alias, groups, "true", buddy->name, sip->contacts_delta++
1409 send_soap_request(sip, body);
1410 g_free(groups);
1411 g_free(body);
1416 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1418 if (msg->response == 200) {
1419 struct sipe_group *group;
1420 struct group_user_context *ctx = trans->payload->data;
1421 xmlnode *xml;
1422 xmlnode *node;
1423 char *group_id;
1424 struct sipe_buddy *buddy;
1426 xml = xmlnode_from_str(msg->body, msg->bodylen);
1427 if (!xml) {
1428 return FALSE;
1431 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1432 if (!node) {
1433 xmlnode_free(xml);
1434 return FALSE;
1437 group_id = xmlnode_get_data(node);
1438 if (!group_id) {
1439 xmlnode_free(xml);
1440 return FALSE;
1443 group = g_new0(struct sipe_group, 1);
1444 group->id = (int)g_ascii_strtod(group_id, NULL);
1445 g_free(group_id);
1446 group->name = g_strdup(ctx->group_name);
1448 sipe_group_add(sip, group);
1450 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1451 if (buddy) {
1452 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1455 sipe_group_set_user(sip, ctx->user_name);
1457 xmlnode_free(xml);
1458 return TRUE;
1460 return FALSE;
1463 static void sipe_group_context_destroy(gpointer data)
1465 struct group_user_context *ctx = data;
1466 g_free(ctx->group_name);
1467 g_free(ctx->user_name);
1468 g_free(ctx);
1471 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1473 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1474 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1475 gchar *body;
1476 ctx->group_name = g_strdup(name);
1477 ctx->user_name = g_strdup(who);
1478 payload->destroy = sipe_group_context_destroy;
1479 payload->data = ctx;
1481 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1482 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1483 g_free(body);
1487 * Data structure for scheduled actions
1490 struct scheduled_action {
1492 * Name of action.
1493 * Format is <Event>[<Data>...]
1494 * Example: <presence><sip:user@domain.com> or <registration>
1496 gchar *name;
1497 guint timeout_handler;
1498 gboolean repetitive;
1499 Action action;
1500 GDestroyNotify destroy;
1501 struct sipe_account_data *sip;
1502 void *payload;
1506 * A timer callback
1507 * Should return FALSE if repetitive action is not needed
1509 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1511 gboolean ret;
1512 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1513 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1514 SIPE_DEBUG_INFO("sip->timeouts count:%d after removal", g_slist_length(sched_action->sip->timeouts));
1515 (sched_action->action)(sched_action->sip, sched_action->payload);
1516 ret = sched_action->repetitive;
1517 if (sched_action->destroy) {
1518 (*sched_action->destroy)(sched_action->payload);
1520 g_free(sched_action->name);
1521 g_free(sched_action);
1522 return ret;
1526 * Kills action timer effectively cancelling
1527 * scheduled action
1529 * @param name of action
1531 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1533 GSList *entry;
1535 if (!sip->timeouts || !name) return;
1537 entry = sip->timeouts;
1538 while (entry) {
1539 struct scheduled_action *sched_action = entry->data;
1540 if(sipe_strequal(sched_action->name, name)) {
1541 GSList *to_delete = entry;
1542 entry = entry->next;
1543 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1544 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1545 purple_timeout_remove(sched_action->timeout_handler);
1546 if (sched_action->destroy) {
1547 (*sched_action->destroy)(sched_action->payload);
1549 g_free(sched_action->name);
1550 g_free(sched_action);
1551 } else {
1552 entry = entry->next;
1557 static void
1558 sipe_schedule_action0(const gchar *name,
1559 int timeout,
1560 gboolean isSeconds,
1561 Action action,
1562 GDestroyNotify destroy,
1563 struct sipe_account_data *sip,
1564 void *payload)
1566 struct scheduled_action *sched_action;
1568 /* Make sure each action only exists once */
1569 sipe_cancel_scheduled_action(sip, name);
1571 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1572 sched_action = g_new0(struct scheduled_action, 1);
1573 sched_action->repetitive = FALSE;
1574 sched_action->name = g_strdup(name);
1575 sched_action->action = action;
1576 sched_action->destroy = destroy;
1577 sched_action->sip = sip;
1578 sched_action->payload = payload;
1579 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1580 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1581 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1582 SIPE_DEBUG_INFO("sip->timeouts count:%d after addition", g_slist_length(sip->timeouts));
1585 void
1586 sipe_schedule_action(const gchar *name,
1587 int timeout,
1588 Action action,
1589 GDestroyNotify destroy,
1590 struct sipe_account_data *sip,
1591 void *payload)
1593 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1597 * Same as sipe_schedule_action() but timeout is in milliseconds.
1599 static void
1600 sipe_schedule_action_msec(const gchar *name,
1601 int timeout,
1602 Action action,
1603 GDestroyNotify destroy,
1604 struct sipe_account_data *sip,
1605 void *payload)
1607 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1610 static void
1611 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1612 time_t calculate_from);
1614 static int
1615 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1617 static const char*
1618 sipe_get_status_by_availability(int avail,
1619 char** activity);
1621 static void
1622 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1623 const char *status_id,
1624 const char *message,
1625 time_t do_not_publish[]);
1627 static void
1628 sipe_apply_calendar_status(struct sipe_account_data *sip,
1629 struct sipe_buddy *sbuddy,
1630 const char *status_id)
1632 time_t cal_avail_since;
1633 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1634 int avail;
1635 gchar *self_uri;
1637 if (!sbuddy) return;
1639 if (cal_status < SIPE_CAL_NO_DATA) {
1640 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1641 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1644 /* scheduled Cal update call */
1645 if (!status_id) {
1646 status_id = sbuddy->last_non_cal_status_id;
1647 g_free(sbuddy->activity);
1648 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1651 if (!status_id) {
1652 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1653 sbuddy->name ? sbuddy->name : "" );
1654 return;
1657 /* adjust to calendar status */
1658 if (cal_status != SIPE_CAL_NO_DATA) {
1659 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1661 if (cal_status == SIPE_CAL_BUSY
1662 && cal_avail_since > sbuddy->user_avail_since
1663 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1665 status_id = SIPE_STATUS_ID_BUSY;
1666 g_free(sbuddy->activity);
1667 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1669 avail = sipe_get_availability_by_status(status_id, NULL);
1671 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1672 if (cal_avail_since > sbuddy->activity_since) {
1673 if (cal_status == SIPE_CAL_OOF
1674 && avail >= 15000) /* 12000 in 2007 */
1676 g_free(sbuddy->activity);
1677 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1682 /* then set status_id actually */
1683 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1684 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1686 /* set our account state to the one in roaming (including calendar info) */
1687 self_uri = sip_uri_self(sip);
1688 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1689 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1690 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1693 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1694 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1696 g_free(self_uri);
1699 static void
1700 sipe_got_user_status(struct sipe_account_data *sip,
1701 const char* uri,
1702 const char *status_id)
1704 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1706 if (!sbuddy) return;
1708 /* Check if on 2005 system contact's calendar,
1709 * then set/preserve it.
1711 if (!sip->ocs2007) {
1712 sipe_apply_calendar_status(sip, sbuddy, status_id);
1713 } else {
1714 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1718 static void
1719 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1720 struct sipe_buddy *sbuddy,
1721 struct sipe_account_data *sip)
1723 sipe_apply_calendar_status(sip, sbuddy, NULL);
1727 * Updates contact's status
1728 * based on their calendar information.
1730 * Applicability: 2005 systems
1732 static void
1733 update_calendar_status(struct sipe_account_data *sip)
1735 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1736 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1738 /* repeat scheduling */
1739 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1743 * Schedules process of contacts' status update
1744 * based on their calendar information.
1745 * Should be scheduled to the beginning of every
1746 * 15 min interval, like:
1747 * 13:00, 13:15, 13:30, 13:45, etc.
1749 * Applicability: 2005 systems
1751 static void
1752 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1753 time_t calculate_from)
1755 int interval = 15*60;
1756 /** start of the beginning of closest 15 min interval. */
1757 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1759 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1760 asctime(localtime(&calculate_from)));
1761 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1762 asctime(localtime(&next_start)));
1764 sipe_schedule_action("<+2005-cal-status>",
1765 (int)(next_start - time(NULL)),
1766 (Action)update_calendar_status,
1767 NULL,
1768 sip,
1769 NULL);
1773 * Schedules process of self status publish
1774 * based on own calendar information.
1775 * Should be scheduled to the beginning of every
1776 * 15 min interval, like:
1777 * 13:00, 13:15, 13:30, 13:45, etc.
1779 * Applicability: 2007+ systems
1781 static void
1782 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1783 time_t calculate_from)
1785 int interval = 5*60;
1786 /** start of the beginning of closest 5 min interval. */
1787 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1789 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1790 asctime(localtime(&calculate_from)));
1791 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1792 asctime(localtime(&next_start)));
1794 sipe_schedule_action("<+2007-cal-status>",
1795 (int)(next_start - time(NULL)),
1796 (Action)publish_calendar_status_self,
1797 NULL,
1798 sip,
1799 NULL);
1802 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1804 /** Should be g_free()'d
1806 static gchar *
1807 sipe_get_subscription_key(const gchar *event,
1808 const gchar *with)
1810 gchar *key = NULL;
1812 if (is_empty(event)) return NULL;
1814 if (event && sipe_strcase_equal(event, "presence")) {
1815 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1816 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1818 /* @TODO drop participated buddies' just_added flag */
1819 } else if (event) {
1820 /* Subscription is identified by <event> key */
1821 key = g_strdup_printf("<%s>", event);
1824 return key;
1827 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1828 SIPE_UNUSED_PARAMETER struct transaction *trans)
1830 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1831 const gchar *event = sipmsg_find_header(msg, "Event");
1832 gchar *key;
1834 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1835 if (!event) {
1836 struct sipmsg *request_msg = trans->msg;
1837 event = sipmsg_find_header(request_msg, "Event");
1840 key = sipe_get_subscription_key(event, with);
1842 /* 200 OK; 481 Call Leg Does Not Exist */
1843 if (key && (msg->response == 200 || msg->response == 481)) {
1844 if (g_hash_table_lookup(sip->subscriptions, key)) {
1845 g_hash_table_remove(sip->subscriptions, key);
1846 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1850 /* create/store subscription dialog if not yet */
1851 if (msg->response == 200) {
1852 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1853 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1855 if (key) {
1856 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1857 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1859 subscription->dialog.callid = g_strdup(callid);
1860 subscription->dialog.cseq = atoi(cseq);
1861 subscription->dialog.with = g_strdup(with);
1862 subscription->event = g_strdup(event);
1863 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1865 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1868 g_free(cseq);
1871 g_free(key);
1872 g_free(with);
1874 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1876 process_incoming_notify(sip, msg, FALSE, FALSE);
1878 return TRUE;
1881 static void sipe_subscribe_resource_uri(const char *name,
1882 SIPE_UNUSED_PARAMETER gpointer value,
1883 gchar **resources_uri)
1885 gchar *tmp = *resources_uri;
1886 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1887 g_free(tmp);
1890 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1892 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1893 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1894 gchar *tmp = *resources_uri;
1896 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1898 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1899 g_free(tmp);
1903 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1904 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1905 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1906 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1907 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1910 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1912 gchar *key;
1913 gchar *contact = get_contact(sip);
1914 gchar *request;
1915 gchar *content;
1916 gchar *require = "";
1917 gchar *accept = "";
1918 gchar *autoextend = "";
1919 gchar *content_type;
1920 struct sip_dialog *dialog;
1922 if (sip->ocs2007) {
1923 require = ", categoryList";
1924 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1925 content_type = "application/msrtc-adrl-categorylist+xml";
1926 content = g_strdup_printf(
1927 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1928 "<action name=\"subscribe\" id=\"63792024\">\n"
1929 "<adhocList>\n%s</adhocList>\n"
1930 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1931 "<category name=\"calendarData\"/>\n"
1932 "<category name=\"contactCard\"/>\n"
1933 "<category name=\"note\"/>\n"
1934 "<category name=\"state\"/>\n"
1935 "</categoryList>\n"
1936 "</action>\n"
1937 "</batchSub>", sip->username, resources_uri);
1938 } else {
1939 autoextend = "Supported: com.microsoft.autoextend\r\n";
1940 content_type = "application/adrl+xml";
1941 content = g_strdup_printf(
1942 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1943 "<create xmlns=\"\">\n%s</create>\n"
1944 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1946 g_free(resources_uri);
1948 request = g_strdup_printf(
1949 "Require: adhoclist%s\r\n"
1950 "Supported: eventlist\r\n"
1951 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1952 "Supported: ms-piggyback-first-notify\r\n"
1953 "%sSupported: ms-benotify\r\n"
1954 "Proxy-Require: ms-benotify\r\n"
1955 "Event: presence\r\n"
1956 "Content-Type: %s\r\n"
1957 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1958 g_free(contact);
1960 /* subscribe to buddy presence */
1961 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1962 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1963 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1964 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
1966 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1968 g_free(content);
1969 g_free(to);
1970 g_free(request);
1971 g_free(key);
1974 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1975 SIPE_UNUSED_PARAMETER void *unused)
1977 gchar *to = sip_uri_self(sip);
1978 gchar *resources_uri = g_strdup("");
1979 if (sip->ocs2007) {
1980 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1981 } else {
1982 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1985 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1988 struct presence_batched_routed {
1989 gchar *host;
1990 GSList *buddies;
1993 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1995 struct presence_batched_routed *data = payload;
1996 GSList *buddies = data->buddies;
1997 while (buddies) {
1998 g_free(buddies->data);
1999 buddies = buddies->next;
2001 g_slist_free(data->buddies);
2002 g_free(data->host);
2003 g_free(payload);
2006 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2008 struct presence_batched_routed *data = payload;
2009 GSList *buddies = data->buddies;
2010 gchar *resources_uri = g_strdup("");
2011 while (buddies) {
2012 gchar *tmp = resources_uri;
2013 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2014 g_free(tmp);
2015 buddies = buddies->next;
2017 sipe_subscribe_presence_batched_to(sip, resources_uri,
2018 g_strdup(data->host));
2022 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2023 * The user sends a single SUBSCRIBE request to the subscribed contact.
2024 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2028 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2031 gchar *key;
2032 gchar *to = sip_uri((char *)buddy_name);
2033 gchar *tmp = get_contact(sip);
2034 gchar *request;
2035 gchar *content = NULL;
2036 gchar *autoextend = "";
2037 gchar *content_type = "";
2038 struct sip_dialog *dialog;
2039 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2040 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2042 if (sbuddy) sbuddy->just_added = FALSE;
2044 if (sip->ocs2007) {
2045 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2046 } else {
2047 autoextend = "Supported: com.microsoft.autoextend\r\n";
2050 request = g_strdup_printf(
2051 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2052 "Supported: ms-piggyback-first-notify\r\n"
2053 "%s%sSupported: ms-benotify\r\n"
2054 "Proxy-Require: ms-benotify\r\n"
2055 "Event: presence\r\n"
2056 "Contact: %s\r\n", autoextend, content_type, tmp);
2058 if (sip->ocs2007) {
2059 content = g_strdup_printf(
2060 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2061 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2062 "<resource uri=\"%s\"%s\n"
2063 "</adhocList>\n"
2064 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2065 "<category name=\"calendarData\"/>\n"
2066 "<category name=\"contactCard\"/>\n"
2067 "<category name=\"note\"/>\n"
2068 "<category name=\"state\"/>\n"
2069 "</categoryList>\n"
2070 "</action>\n"
2071 "</batchSub>", sip->username, to, context);
2074 g_free(tmp);
2076 /* subscribe to buddy presence */
2077 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2078 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2079 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2080 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2082 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2084 g_free(content);
2085 g_free(to);
2086 g_free(request);
2087 g_free(key);
2090 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2092 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
2094 if (!purple_status_is_active(status))
2095 return;
2097 if (account->gc) {
2098 struct sipe_account_data *sip = account->gc->proto_data;
2100 if (sip) {
2101 gchar *action_name;
2102 gchar *tmp;
2103 time_t now = time(NULL);
2104 const char *status_id = purple_status_get_id(status);
2105 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2106 sipe_activity activity = sipe_get_activity_by_token(status_id);
2107 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2109 /* when other point of presence clears note, but we are keeping
2110 * state if OOF note.
2112 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2113 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2114 do_not_publish = FALSE;
2117 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2118 status_id, (int)sip->do_not_publish[activity], (int)now);
2120 sip->do_not_publish[activity] = 0;
2121 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2122 status_id, (int)sip->do_not_publish[activity]);
2124 if (do_not_publish)
2126 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2127 return;
2130 g_free(sip->status);
2131 sip->status = g_strdup(status_id);
2133 /* hack to escape apostrof before comparison */
2134 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
2136 /* this will preserve OOF flag as well */
2137 if (!sipe_strequal(tmp, sip->note)) {
2138 sip->is_oof_note = FALSE;
2139 g_free(sip->note);
2140 sip->note = g_strdup(note);
2141 sip->note_since = time(NULL);
2143 g_free(tmp);
2145 /* schedule 2 sec to capture idle flag */
2146 action_name = g_strdup_printf("<%s>", "+set-status");
2147 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2148 g_free(action_name);
2152 static void
2153 sipe_set_idle(PurpleConnection * gc,
2154 int interval)
2156 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
2158 if (gc) {
2159 struct sipe_account_data *sip = gc->proto_data;
2161 if (sip) {
2162 sip->idle_switch = time(NULL);
2163 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2168 static void
2169 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2170 SIPE_UNUSED_PARAMETER const char *alias)
2172 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2173 sipe_group_set_user(sip, name);
2176 static void
2177 sipe_group_buddy(PurpleConnection *gc,
2178 const char *who,
2179 const char *old_group_name,
2180 const char *new_group_name)
2182 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2183 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2184 struct sipe_group * old_group = NULL;
2185 struct sipe_group * new_group;
2187 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2188 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2190 if(!buddy) { // buddy not in roaming list
2191 return;
2194 if (old_group_name) {
2195 old_group = sipe_group_find_by_name(sip, old_group_name);
2197 new_group = sipe_group_find_by_name(sip, new_group_name);
2199 if (old_group) {
2200 buddy->groups = g_slist_remove(buddy->groups, old_group);
2201 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
2204 if (!new_group) {
2205 sipe_group_create(sip, new_group_name, who);
2206 } else {
2207 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2208 sipe_group_set_user(sip, who);
2212 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2214 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2216 /* libpurple can call us with undefined buddy or group */
2217 if (buddy && group) {
2218 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2220 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2221 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2222 purple_blist_rename_buddy(buddy, buddy_name);
2223 g_free(buddy_name);
2225 /* Prepend sip: if needed */
2226 if (!g_str_has_prefix(buddy->name, "sip:")) {
2227 gchar *buf = sip_uri_from_name(buddy->name);
2228 purple_blist_rename_buddy(buddy, buf);
2229 g_free(buf);
2232 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2233 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2234 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
2235 b->name = g_strdup(buddy->name);
2236 b->just_added = TRUE;
2237 g_hash_table_insert(sip->buddies, b->name, b);
2238 sipe_group_buddy(gc, b->name, NULL, group->name);
2239 /* @TODO should go to callback */
2240 sipe_subscribe_presence_single(sip, b->name);
2241 } else {
2242 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
2247 static void sipe_free_buddy(struct sipe_buddy *buddy)
2249 #ifndef _WIN32
2251 * We are calling g_hash_table_foreach_steal(). That means that no
2252 * key/value deallocation functions are called. Therefore the glib
2253 * hash code does not touch the key (buddy->name) or value (buddy)
2254 * of the to-be-deleted hash node at all. It follows that we
2256 * - MUST free the memory for the key ourselves and
2257 * - ARE allowed to do it in this function
2259 * Conclusion: glib must be broken on the Windows platform if sipe
2260 * crashes with SIGTRAP when closing. You'll have to live
2261 * with the memory leak until this is fixed.
2263 g_free(buddy->name);
2264 #endif
2265 g_free(buddy->activity);
2266 g_free(buddy->meeting_subject);
2267 g_free(buddy->meeting_location);
2268 g_free(buddy->note);
2270 g_free(buddy->cal_start_time);
2271 g_free(buddy->cal_free_busy_base64);
2272 g_free(buddy->cal_free_busy);
2273 g_free(buddy->last_non_cal_activity);
2275 sipe_cal_free_working_hours(buddy->cal_working_hours);
2277 g_free(buddy->device_name);
2278 g_slist_free(buddy->groups);
2279 g_free(buddy);
2283 * Unassociates buddy from group first.
2284 * Then see if no groups left, removes buddy completely.
2285 * Otherwise updates buddy groups on server.
2287 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2289 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2290 struct sipe_buddy *b;
2291 struct sipe_group *g = NULL;
2293 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2294 if (!buddy) return;
2296 b = g_hash_table_lookup(sip->buddies, buddy->name);
2297 if (!b) return;
2299 if (group) {
2300 g = sipe_group_find_by_name(sip, group->name);
2303 if (g) {
2304 b->groups = g_slist_remove(b->groups, g);
2305 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2308 if (g_slist_length(b->groups) < 1) {
2309 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2310 sipe_cancel_scheduled_action(sip, action_name);
2311 g_free(action_name);
2313 g_hash_table_remove(sip->buddies, buddy->name);
2315 if (b->name) {
2316 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2317 send_soap_request(sip, body);
2318 g_free(body);
2321 sipe_free_buddy(b);
2322 } else {
2323 //updates groups on server
2324 sipe_group_set_user(sip, b->name);
2329 static void
2330 sipe_rename_group(PurpleConnection *gc,
2331 const char *old_name,
2332 PurpleGroup *group,
2333 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2335 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2336 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2337 if (s_group) {
2338 sipe_group_rename(sip, s_group, group->name);
2339 } else {
2340 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2344 static void
2345 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2347 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2348 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2349 if (s_group) {
2350 gchar *body;
2351 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2352 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2353 send_soap_request(sip, body);
2354 g_free(body);
2356 sip->groups = g_slist_remove(sip->groups, s_group);
2357 g_free(s_group->name);
2358 g_free(s_group);
2359 } else {
2360 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2364 /** All statuses need message attribute to pass Note */
2365 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2367 PurpleStatusType *type;
2368 GList *types = NULL;
2370 /* Macros to reduce code repetition.
2371 Translators: noun */
2372 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2373 prim, id, name, \
2374 TRUE, user, FALSE, \
2375 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2376 NULL); \
2377 types = g_list_append(types, type);
2379 /* Online */
2380 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2381 NULL,
2382 NULL,
2383 TRUE);
2385 /* Busy */
2386 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2387 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2388 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2389 TRUE);
2391 /* Do Not Disturb */
2392 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2393 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2394 NULL,
2395 TRUE);
2397 /* Away */
2398 /* Goes first in the list as
2399 * purple picks the first status with the AWAY type
2400 * for idle.
2402 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2403 NULL,
2404 NULL,
2405 TRUE);
2407 /* Be Right Back */
2408 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2409 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2410 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2411 TRUE);
2413 /* Appear Offline */
2414 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2415 NULL,
2416 NULL,
2417 TRUE);
2419 /* Offline */
2420 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2421 NULL,
2422 NULL,
2423 TRUE);
2424 types = g_list_append(types, type);
2426 return types;
2430 * A callback for g_hash_table_foreach
2432 static void
2433 sipe_buddy_subscribe_cb(char *buddy_name,
2434 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2435 struct sipe_account_data *sip)
2437 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2438 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2439 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2440 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2442 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2443 g_free(action_name);
2447 * Removes entries from purple buddy list
2448 * that does not correspond ones in the roaming contact list.
2450 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2451 GSList *buddies = purple_find_buddies(sip->account, NULL);
2452 GSList *entry = buddies;
2453 struct sipe_buddy *buddy;
2454 PurpleBuddy *b;
2455 PurpleGroup *g;
2457 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2458 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sip->buddies));
2459 while (entry) {
2460 b = entry->data;
2461 g = purple_buddy_get_group(b);
2462 buddy = g_hash_table_lookup(sip->buddies, b->name);
2463 if(buddy) {
2464 gboolean in_sipe_groups = FALSE;
2465 GSList *entry2 = buddy->groups;
2466 while (entry2) {
2467 struct sipe_group *group = entry2->data;
2468 if (sipe_strequal(group->name, g->name)) {
2469 in_sipe_groups = TRUE;
2470 break;
2472 entry2 = entry2->next;
2474 if(!in_sipe_groups) {
2475 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2476 purple_blist_remove_buddy(b);
2478 } else {
2479 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2480 purple_blist_remove_buddy(b);
2482 entry = entry->next;
2484 g_slist_free(buddies);
2487 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2489 int len = msg->bodylen;
2491 const gchar *tmp = sipmsg_find_header(msg, "Event");
2492 xmlnode *item;
2493 xmlnode *isc;
2494 const gchar *contacts_delta;
2495 xmlnode *group_node;
2496 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2497 return FALSE;
2500 /* Convert the contact from XML to Purple Buddies */
2501 isc = xmlnode_from_str(msg->body, len);
2502 if (!isc) {
2503 return FALSE;
2506 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2507 if (contacts_delta) {
2508 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2511 if (sipe_strequal(isc->name, "contactList")) {
2513 /* Parse groups */
2514 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2515 struct sipe_group * group = g_new0(struct sipe_group, 1);
2516 const char *name = xmlnode_get_attrib(group_node, "name");
2518 if (g_str_has_prefix(name, "~")) {
2519 name = _("Other Contacts");
2521 group->name = g_strdup(name);
2522 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2524 sipe_group_add(sip, group);
2527 // Make sure we have at least one group
2528 if (g_slist_length(sip->groups) == 0) {
2529 struct sipe_group * group = g_new0(struct sipe_group, 1);
2530 PurpleGroup *purple_group;
2531 group->name = g_strdup(_("Other Contacts"));
2532 group->id = 1;
2533 purple_group = purple_group_new(group->name);
2534 purple_blist_add_group(purple_group, NULL);
2535 sip->groups = g_slist_append(sip->groups, group);
2538 /* Parse contacts */
2539 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2540 const gchar *uri = xmlnode_get_attrib(item, "uri");
2541 const gchar *name = xmlnode_get_attrib(item, "name");
2542 gchar *buddy_name;
2543 struct sipe_buddy *buddy = NULL;
2544 gchar *tmp;
2545 gchar **item_groups;
2546 int i = 0;
2548 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2549 tmp = sip_uri_from_name(uri);
2550 buddy_name = g_ascii_strdown(tmp, -1);
2551 g_free(tmp);
2553 /* assign to group Other Contacts if nothing else received */
2554 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2555 if(is_empty(tmp)) {
2556 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2557 g_free(tmp);
2558 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2560 item_groups = g_strsplit(tmp, " ", 0);
2561 g_free(tmp);
2563 while (item_groups[i]) {
2564 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2566 // If couldn't find the right group for this contact, just put them in the first group we have
2567 if (group == NULL && g_slist_length(sip->groups) > 0) {
2568 group = sip->groups->data;
2571 if (group != NULL) {
2572 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2573 if (!b){
2574 b = purple_buddy_new(sip->account, buddy_name, uri);
2575 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2577 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2580 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2581 if (name != NULL && strlen(name) != 0) {
2582 purple_blist_alias_buddy(b, name);
2584 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2588 if (!buddy) {
2589 buddy = g_new0(struct sipe_buddy, 1);
2590 buddy->name = g_strdup(b->name);
2591 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2594 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2596 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2597 } else {
2598 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2599 name);
2602 i++;
2603 } // while, contact groups
2604 g_strfreev(item_groups);
2605 g_free(buddy_name);
2607 } // for, contacts
2609 sipe_cleanup_local_blist(sip);
2611 /* Add self-contact if not there yet. 2005 systems. */
2612 /* This will resemble subscription to roaming_self in 2007 systems */
2613 if (!sip->ocs2007) {
2614 gchar *self_uri = sip_uri_self(sip);
2615 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2617 if (!buddy) {
2618 buddy = g_new0(struct sipe_buddy, 1);
2619 buddy->name = g_strdup(self_uri);
2620 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2622 g_free(self_uri);
2625 xmlnode_free(isc);
2627 /* subscribe to buddies */
2628 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2629 if (sip->batched_support) {
2630 sipe_subscribe_presence_batched(sip, NULL);
2631 } else {
2632 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2634 sip->subscribed_buddies = TRUE;
2636 /* for 2005 systems schedule contacts' status update
2637 * based on their calendar information
2639 if (!sip->ocs2007) {
2640 sipe_sched_calendar_status_update(sip, time(NULL));
2643 return 0;
2647 * Subscribe roaming contacts
2649 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2651 gchar *to = sip_uri_self(sip);
2652 gchar *tmp = get_contact(sip);
2653 gchar *hdr = g_strdup_printf(
2654 "Event: vnd-microsoft-roaming-contacts\r\n"
2655 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2656 "Supported: com.microsoft.autoextend\r\n"
2657 "Supported: ms-benotify\r\n"
2658 "Proxy-Require: ms-benotify\r\n"
2659 "Supported: ms-piggyback-first-notify\r\n"
2660 "Contact: %s\r\n", tmp);
2661 g_free(tmp);
2663 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2664 g_free(to);
2665 g_free(hdr);
2668 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2669 SIPE_UNUSED_PARAMETER void *unused)
2671 gchar *key;
2672 struct sip_dialog *dialog;
2673 gchar *to = sip_uri_self(sip);
2674 gchar *tmp = get_contact(sip);
2675 gchar *hdr = g_strdup_printf(
2676 "Event: presence.wpending\r\n"
2677 "Accept: text/xml+msrtc.wpending\r\n"
2678 "Supported: com.microsoft.autoextend\r\n"
2679 "Supported: ms-benotify\r\n"
2680 "Proxy-Require: ms-benotify\r\n"
2681 "Supported: ms-piggyback-first-notify\r\n"
2682 "Contact: %s\r\n", tmp);
2683 g_free(tmp);
2685 /* Subscription is identified by <event> key */
2686 key = g_strdup_printf("<%s>", "presence.wpending");
2687 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2688 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2690 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2692 g_free(to);
2693 g_free(hdr);
2694 g_free(key);
2698 * Fires on deregistration event initiated by server.
2699 * [MS-SIPREGE] SIP extension.
2702 // 2007 Example
2704 // Content-Type: text/registration-event
2705 // subscription-state: terminated;expires=0
2706 // ms-diagnostics-public: 4141;reason="User disabled"
2708 // deregistered;event=rejected
2710 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2712 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2713 gchar *event = NULL;
2714 gchar *reason = NULL;
2715 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2716 gchar *warning;
2718 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2719 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2721 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2722 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2723 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2724 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2725 } else {
2726 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2727 return;
2730 if (diagnostics != NULL) {
2731 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2732 } else { // for LCS2005
2733 int error_id = 0;
2734 if (event && sipe_strcase_equal(event, "unregistered")) {
2735 error_id = 4140; // [MS-SIPREGE]
2736 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2737 reason = g_strdup(_("you are already signed in at another location"));
2738 } else if (event && sipe_strcase_equal(event, "rejected")) {
2739 error_id = 4141;
2740 reason = g_strdup(_("user disabled")); // [MS-OCER]
2741 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2742 error_id = 4142;
2743 reason = g_strdup(_("user moved")); // [MS-OCER]
2746 g_free(event);
2747 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2748 g_free(reason);
2750 sip->gc->wants_to_die = TRUE;
2751 purple_connection_error(sip->gc, warning);
2752 g_free(warning);
2756 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2758 xmlnode *xn_provision_group_list;
2759 xmlnode *node;
2761 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2763 /* provisionGroup */
2764 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2765 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2766 g_free(sip->focus_factory_uri);
2767 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2768 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2769 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2770 break;
2773 xmlnode_free(xn_provision_group_list);
2776 /** for 2005 system */
2777 static void
2778 sipe_process_provisioning(struct sipe_account_data *sip,
2779 struct sipmsg *msg)
2781 xmlnode *xn_provision;
2782 xmlnode *node;
2784 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2785 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2786 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", xmlnode_get_attrib(node, "uri"));
2787 if ((node = xmlnode_get_child(node, "line"))) {
2788 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2789 const gchar *server = xmlnode_get_attrib(node, "server");
2790 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2791 sip_csta_open(sip, line_uri, server);
2794 xmlnode_free(xn_provision);
2797 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2799 const gchar *contacts_delta;
2800 xmlnode *xml;
2802 xml = xmlnode_from_str(msg->body, msg->bodylen);
2803 if (!xml)
2805 return;
2808 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2809 if (contacts_delta)
2811 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2814 xmlnode_free(xml);
2817 static void
2818 free_container(struct sipe_container *container)
2820 GSList *entry;
2822 if (!container) return;
2824 entry = container->members;
2825 while (entry) {
2826 void *data = entry->data;
2827 entry = g_slist_remove(entry, data);
2828 g_free(data);
2830 g_free(container);
2834 * Finds locally stored MS-PRES container member
2836 static struct sipe_container_member *
2837 sipe_find_container_member(struct sipe_container *container,
2838 const gchar *type,
2839 const gchar *value)
2841 struct sipe_container_member *member;
2842 GSList *entry;
2844 if (container == NULL || type == NULL) {
2845 return NULL;
2848 entry = container->members;
2849 while (entry) {
2850 member = entry->data;
2851 if (!g_strcasecmp(member->type, type)
2852 && ((!member->value && !value)
2853 || (value && member->value && !g_strcasecmp(member->value, value)))
2855 return member;
2857 entry = entry->next;
2859 return NULL;
2863 * Finds locally stored MS-PRES container by id
2865 static struct sipe_container *
2866 sipe_find_container(struct sipe_account_data *sip,
2867 guint id)
2869 struct sipe_container *container;
2870 GSList *entry;
2872 if (sip == NULL) {
2873 return NULL;
2876 entry = sip->containers;
2877 while (entry) {
2878 container = entry->data;
2879 if (id == container->id) {
2880 return container;
2882 entry = entry->next;
2884 return NULL;
2888 * Access Levels
2889 * 32000 - Blocked
2890 * 400 - Personal
2891 * 300 - Team
2892 * 200 - Company
2893 * 100 - Public
2895 static int
2896 sipe_find_access_level(struct sipe_account_data *sip,
2897 const gchar *type,
2898 const gchar *value)
2900 guint containers[] = {32000, 400, 300, 200, 100};
2901 int i = 0;
2903 for (i = 0; i < 5; i++) {
2904 struct sipe_container_member *member;
2905 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2906 if (!container) continue;
2908 member = sipe_find_container_member(container, type, value);
2909 if (member) {
2910 return containers[i];
2914 return -1;
2917 static void
2918 sipe_send_set_container_members(struct sipe_account_data *sip,
2919 guint container_id,
2920 guint container_version,
2921 const gchar* action,
2922 const gchar* type,
2923 const gchar* value)
2925 gchar *self = sip_uri_self(sip);
2926 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2927 gchar *contact;
2928 gchar *hdr;
2929 gchar *body = g_strdup_printf(
2930 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2931 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2932 "</setContainerMembers>",
2933 container_id,
2934 container_version,
2935 action,
2936 type,
2937 value_str);
2938 g_free(value_str);
2940 contact = get_contact(sip);
2941 hdr = g_strdup_printf("Contact: %s\r\n"
2942 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2943 g_free(contact);
2945 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2947 g_free(hdr);
2948 g_free(body);
2949 g_free(self);
2952 static void
2953 free_publication(struct sipe_publication *publication)
2955 g_free(publication->category);
2956 g_free(publication->cal_event_hash);
2957 g_free(publication->note);
2959 g_free(publication->working_hours_xml_str);
2960 g_free(publication->fb_start_str);
2961 g_free(publication->free_busy_base64);
2963 g_free(publication);
2966 /* key is <category><instance><container> */
2967 static gboolean
2968 sipe_is_our_publication(struct sipe_account_data *sip,
2969 const gchar *key)
2971 GSList *entry;
2973 /* filling keys for our publications if not yet cached */
2974 if (!sip->our_publication_keys) {
2975 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2976 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2977 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2978 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2979 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2980 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2981 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2983 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2984 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2985 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2986 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2987 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2988 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2989 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2990 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2991 SIPE_DEBUG_INFO("\tNote : %u", 0);
2992 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2994 /* device */
2995 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2996 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2998 /* state:machineState */
2999 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3000 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3001 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3002 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3004 /* state:userState */
3005 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3006 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3007 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3008 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3010 /* state:calendarState */
3011 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3012 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3013 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3014 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3016 /* state:calendarState OOF */
3017 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3018 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3019 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3020 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3022 /* note */
3023 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3024 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3025 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3026 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3027 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3028 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3030 /* note OOF */
3031 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3032 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3033 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3034 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3035 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3036 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3038 /* calendarData:WorkingHours */
3039 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3040 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3041 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3042 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3043 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3044 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3045 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3046 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3047 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3048 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3049 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3050 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3052 /* calendarData:FreeBusy */
3053 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3054 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3055 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3056 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3057 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3058 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3059 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3060 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3061 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3062 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3063 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3064 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3066 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3067 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3070 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3072 entry = sip->our_publication_keys;
3073 while (entry) {
3074 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3075 if (sipe_strequal(entry->data, key)) {
3076 return TRUE;
3078 entry = entry->next;
3080 return FALSE;
3083 /** Property names to store in blist.xml */
3084 #define ALIAS_PROP "alias"
3085 #define EMAIL_PROP "email"
3086 #define PHONE_PROP "phone"
3087 #define PHONE_DISPLAY_PROP "phone-display"
3088 #define PHONE_MOBILE_PROP "phone-mobile"
3089 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3090 #define PHONE_HOME_PROP "phone-home"
3091 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3092 #define PHONE_OTHER_PROP "phone-other"
3093 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3094 #define PHONE_CUSTOM1_PROP "phone-custom1"
3095 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3096 #define SITE_PROP "site"
3097 #define COMPANY_PROP "company"
3098 #define DEPARTMENT_PROP "department"
3099 #define TITLE_PROP "title"
3100 #define OFFICE_PROP "office"
3101 /** implies work address */
3102 #define ADDRESS_STREET_PROP "address-street"
3103 #define ADDRESS_CITY_PROP "address-city"
3104 #define ADDRESS_STATE_PROP "address-state"
3105 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3106 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3109 * Tries to figure out user first and last name
3110 * based on Display Name and email properties.
3112 * Allocates memory - must be g_free()'d
3114 * Examples to parse:
3115 * First Last
3116 * First Last - Company Name
3117 * Last, First
3118 * Last, First M.
3119 * Last, First (C)(STP) (Company)
3120 * first.last@company.com (preprocessed as "first last")
3121 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3123 * Unusable examples:
3124 * user@company.com (preprocessed as "user")
3125 * first.m.last@company.com (preprocessed as "first m last")
3126 * user.company.com@reuters.net (preprocessed as "user company com")
3128 static void
3129 sipe_get_first_last_names(struct sipe_account_data *sip,
3130 const char *uri,
3131 char **first_name,
3132 char **last_name)
3134 PurpleBuddy *p_buddy;
3135 char *display_name;
3136 const char *email;
3137 const char *first, *last;
3138 char *tmp;
3139 char **parts;
3140 gboolean has_comma = FALSE;
3142 if (!sip || !uri) return;
3144 p_buddy = purple_find_buddy(sip->account, uri);
3146 if (!p_buddy) return;
3148 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3149 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3151 if (!display_name && !email) return;
3153 /* if no display name, make "first last anything_else" out of email */
3154 if (email && !display_name) {
3155 display_name = g_strndup(email, strstr(email, "@") - email);
3156 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3157 g_free(tmp);
3160 if (display_name) {
3161 has_comma = (strstr(display_name, ",") != NULL);
3162 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3163 g_free(tmp);
3164 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3165 g_free(tmp);
3168 parts = g_strsplit(display_name, " ", 0);
3170 if (!parts[0] || !parts[1]) {
3171 g_free(display_name);
3172 g_strfreev(parts);
3173 return;
3176 if (has_comma) {
3177 last = parts[0];
3178 first = parts[1];
3179 } else {
3180 first = parts[0];
3181 last = parts[1];
3184 if (first_name) {
3185 *first_name = g_strstrip(g_strdup(first));
3188 if (last_name) {
3189 *last_name = g_strstrip(g_strdup(last));
3192 g_free(display_name);
3193 g_strfreev(parts);
3197 * Update user information
3199 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3200 * @param property_name
3201 * @param property_value may be modified to strip white space
3203 static void
3204 sipe_update_user_info(struct sipe_account_data *sip,
3205 const char *uri,
3206 const char *property_name,
3207 char *property_value)
3209 GSList *buddies, *entry;
3211 if (!property_name || strlen(property_name) == 0) return;
3213 if (property_value)
3214 property_value = g_strstrip(property_value);
3216 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3217 while (entry) {
3218 const char *prop_str;
3219 const char *server_alias;
3220 PurpleBuddy *p_buddy = entry->data;
3222 /* for Display Name */
3223 if (sipe_strequal(property_name, ALIAS_PROP)) {
3224 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3225 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3226 purple_blist_alias_buddy(p_buddy, property_value);
3229 server_alias = purple_buddy_get_server_alias(p_buddy);
3230 if (!is_empty(property_value) &&
3231 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3233 purple_blist_server_alias_buddy(p_buddy, property_value);
3236 /* for other properties */
3237 else {
3238 if (!is_empty(property_value)) {
3239 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3240 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3241 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3246 entry = entry->next;
3248 g_slist_free(buddies);
3252 * Update user phone
3253 * Suitable for both 2005 and 2007 systems.
3255 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3256 * @param phone_type
3257 * @param phone may be modified to strip white space
3258 * @param phone_display_string may be modified to strip white space
3260 static void
3261 sipe_update_user_phone(struct sipe_account_data *sip,
3262 const char *uri,
3263 const gchar *phone_type,
3264 gchar *phone,
3265 gchar *phone_display_string)
3267 const char *phone_node = PHONE_PROP; /* work phone by default */
3268 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3270 if(!phone || strlen(phone) == 0) return;
3272 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3273 phone_node = PHONE_MOBILE_PROP;
3274 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3275 } else if (sipe_strequal(phone_type, "home")) {
3276 phone_node = PHONE_HOME_PROP;
3277 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3278 } else if (sipe_strequal(phone_type, "other")) {
3279 phone_node = PHONE_OTHER_PROP;
3280 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3281 } else if (sipe_strequal(phone_type, "custom1")) {
3282 phone_node = PHONE_CUSTOM1_PROP;
3283 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3286 sipe_update_user_info(sip, uri, phone_node, phone);
3287 if (phone_display_string) {
3288 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3292 static void
3293 sipe_update_calendar(struct sipe_account_data *sip)
3295 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3297 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3299 if (sipe_strequal(calendar, "EXCH")) {
3300 sipe_ews_update_calendar(sip);
3303 /* schedule repeat */
3304 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3306 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3310 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3311 * by using standard Purple's means of signals and saved statuses.
3313 * Thus all UI elements get updated: Status Button with Note, docklet.
3314 * This is ablolutely important as both our status and note can come
3315 * inbound (roaming) or be updated programmatically (e.g. based on our
3316 * calendar data).
3318 static void
3319 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3320 const char *status_id,
3321 const char *message,
3322 time_t do_not_publish[])
3324 PurpleStatus *status = purple_account_get_active_status(account);
3325 gboolean changed = TRUE;
3327 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3328 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3330 changed = FALSE;
3333 if (purple_savedstatus_is_idleaway()) {
3334 changed = FALSE;
3337 if (changed) {
3338 PurpleSavedStatus *saved_status;
3339 const PurpleStatusType *acct_status_type =
3340 purple_status_type_find_with_id(account->status_types, status_id);
3341 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3342 sipe_activity activity = sipe_get_activity_by_token(status_id);
3344 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3345 if (saved_status) {
3346 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3349 /* If this type+message is unique then create a new transient saved status
3350 * Ref: gtkstatusbox.c
3352 if (!saved_status) {
3353 GList *tmp;
3354 GList *active_accts = purple_accounts_get_all_active();
3356 saved_status = purple_savedstatus_new(NULL, primitive);
3357 purple_savedstatus_set_message(saved_status, message);
3359 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3360 purple_savedstatus_set_substatus(saved_status,
3361 (PurpleAccount *)tmp->data, acct_status_type, message);
3363 g_list_free(active_accts);
3366 do_not_publish[activity] = time(NULL);
3367 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3368 status_id, (int)do_not_publish[activity]);
3370 /* Set the status for each account */
3371 purple_savedstatus_activate(saved_status);
3375 struct hash_table_delete_payload {
3376 GHashTable *hash_table;
3377 guint container;
3380 static void
3381 sipe_remove_category_container_publications_cb(const char *name,
3382 struct sipe_publication *publication,
3383 struct hash_table_delete_payload *payload)
3385 if (publication->container == payload->container) {
3386 g_hash_table_remove(payload->hash_table, name);
3389 static void
3390 sipe_remove_category_container_publications(GHashTable *our_publications,
3391 const char *category,
3392 guint container)
3394 struct hash_table_delete_payload payload;
3395 payload.hash_table = g_hash_table_lookup(our_publications, category);
3397 if (!payload.hash_table) return;
3399 payload.container = container;
3400 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3403 static void
3404 send_publish_category_initial(struct sipe_account_data *sip);
3407 * When we receive some self (BE) NOTIFY with a new subscriber
3408 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3411 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3413 gchar *contact;
3414 gchar *to;
3415 xmlnode *xml;
3416 xmlnode *node;
3417 xmlnode *node2;
3418 char *display_name = NULL;
3419 char *uri;
3420 GSList *category_names = NULL;
3421 int aggreg_avail = 0;
3422 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3423 gboolean do_update_status = FALSE;
3424 gboolean has_note_cleaned = FALSE;
3426 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3428 xml = xmlnode_from_str(msg->body, msg->bodylen);
3429 if (!xml) return;
3431 contact = get_contact(sip);
3432 to = sip_uri_self(sip);
3435 /* categories */
3436 /* set list of categories participating in this XML */
3437 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3438 const gchar *name = xmlnode_get_attrib(node, "name");
3439 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3441 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3442 category_names ? (int) g_slist_length(category_names) : -1);
3443 /* drop category information */
3444 if (category_names) {
3445 GSList *entry = category_names;
3446 while (entry) {
3447 GHashTable *cat_publications;
3448 const gchar *category = entry->data;
3449 entry = entry->next;
3450 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3451 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3452 if (cat_publications) {
3453 g_hash_table_remove(sip->our_publications, category);
3454 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3458 g_slist_free(category_names);
3459 /* filling our categories reflected in roaming data */
3460 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3461 const char *tmp;
3462 const gchar *name = xmlnode_get_attrib(node, "name");
3463 guint container = xmlnode_get_int_attrib(node, "container", -1);
3464 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3465 guint version = xmlnode_get_int_attrib(node, "version", 0);
3466 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3467 sipe_utils_str_to_time(tmp) : 0;
3468 gchar *key;
3469 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3471 /* Ex. clear note: <category name="note"/> */
3472 if (container == (guint)-1) {
3473 g_free(sip->note);
3474 sip->note = NULL;
3475 do_update_status = TRUE;
3476 continue;
3479 /* Ex. clear note: <category name="note" container="200"/> */
3480 if (instance == (guint)-1) {
3481 if (container == 200) {
3482 g_free(sip->note);
3483 sip->note = NULL;
3484 do_update_status = TRUE;
3486 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3487 sipe_remove_category_container_publications(
3488 sip->our_publications, name, container);
3489 continue;
3492 /* key is <category><instance><container> */
3493 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3494 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3496 /* capture all userState publication for later clean up if required */
3497 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3498 xmlnode *xn_state = xmlnode_get_child(node, "state");
3500 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3501 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3502 publication->category = g_strdup(name);
3503 publication->instance = instance;
3504 publication->container = container;
3505 publication->version = version;
3507 if (!sip->user_state_publications) {
3508 sip->user_state_publications = g_hash_table_new_full(
3509 g_str_hash, g_str_equal,
3510 g_free, (GDestroyNotify)free_publication);
3512 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3513 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3514 key, version);
3518 if (sipe_is_our_publication(sip, key)) {
3519 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3521 publication->category = g_strdup(name);
3522 publication->instance = instance;
3523 publication->container = container;
3524 publication->version = version;
3526 /* filling publication->availability */
3527 if (sipe_strequal(name, "state")) {
3528 xmlnode *xn_state = xmlnode_get_child(node, "state");
3529 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3531 if (xn_avail) {
3532 gchar *avail_str = xmlnode_get_data(xn_avail);
3533 if (avail_str) {
3534 publication->availability = atoi(avail_str);
3536 g_free(avail_str);
3538 /* for calendarState */
3539 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3540 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3541 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3543 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3544 if (xn_activity) {
3545 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3546 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3548 event->is_meeting = TRUE;
3551 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3552 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3554 publication->cal_event_hash = sipe_cal_event_hash(event);
3555 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3556 publication->cal_event_hash);
3557 sipe_cal_event_free(event);
3560 /* filling publication->note */
3561 if (sipe_strequal(name, "note")) {
3562 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3564 if (!has_note_cleaned) {
3565 has_note_cleaned = TRUE;
3567 g_free(sip->note);
3568 sip->note = NULL;
3569 sip->note_since = publish_time;
3571 do_update_status = TRUE;
3574 g_free(publication->note);
3575 publication->note = NULL;
3576 if (xn_body) {
3577 char *tmp;
3579 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3580 g_free(tmp);
3581 if (publish_time >= sip->note_since) {
3582 g_free(sip->note);
3583 sip->note = g_strdup(publication->note);
3584 sip->note_since = publish_time;
3585 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3587 do_update_status = TRUE;
3592 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3593 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3594 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3595 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3596 if (xn_free_busy) {
3597 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3598 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3600 if (xn_working_hours) {
3601 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3605 if (!cat_publications) {
3606 cat_publications = g_hash_table_new_full(
3607 g_str_hash, g_str_equal,
3608 g_free, (GDestroyNotify)free_publication);
3609 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3610 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3612 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3613 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3615 g_free(key);
3617 /* aggregateState (not an our publication) from 2-nd container */
3618 if (sipe_strequal(name, "state") && container == 2) {
3619 xmlnode *xn_state = xmlnode_get_child(node, "state");
3621 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3622 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3623 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3625 if (xn_avail) {
3626 gchar *avail_str = xmlnode_get_data(xn_avail);
3627 if (avail_str) {
3628 aggreg_avail = atoi(avail_str);
3630 g_free(avail_str);
3633 if (xn_activity) {
3634 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3636 aggreg_activity = sipe_get_activity_by_token(activity_token);
3639 do_update_status = TRUE;
3643 /* userProperties published by server from AD */
3644 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3645 xmlnode *line;
3646 /* line, for Remote Call Control (RCC) */
3647 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3648 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3649 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3650 gchar *line_uri;
3652 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3654 line_uri = xmlnode_get_data(line);
3655 if (line_uri) {
3656 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3657 sip_csta_open(sip, line_uri, line_server);
3659 g_free(line_uri);
3661 break;
3665 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3666 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3668 /* containers */
3669 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3670 guint id = xmlnode_get_int_attrib(node, "id", 0);
3671 struct sipe_container *container = sipe_find_container(sip, id);
3673 if (container) {
3674 sip->containers = g_slist_remove(sip->containers, container);
3675 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3676 free_container(container);
3678 container = g_new0(struct sipe_container, 1);
3679 container->id = id;
3680 container->version = xmlnode_get_int_attrib(node, "version", 0);
3681 sip->containers = g_slist_append(sip->containers, container);
3682 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3684 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3685 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3686 member->type = xmlnode_get_attrib(node2, "type");
3687 member->value = xmlnode_get_attrib(node2, "value");
3688 container->members = g_slist_append(container->members, member);
3689 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3690 member->type, member->value ? member->value : "");
3694 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3695 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3696 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3697 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3698 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3699 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3700 /* initial set-up to let counterparties see your status */
3701 if (sameEnterpriseAL < 0) {
3702 struct sipe_container *container = sipe_find_container(sip, 200);
3703 guint version = container ? container->version : 0;
3704 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3706 if (federatedAL < 0) {
3707 struct sipe_container *container = sipe_find_container(sip, 100);
3708 guint version = container ? container->version : 0;
3709 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3711 sip->access_level_set = TRUE;
3714 /* subscribers */
3715 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3716 const char *user;
3717 const char *acknowledged;
3718 gchar *hdr;
3719 gchar *body;
3721 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3722 if (!user) continue;
3723 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
3724 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3725 uri = sip_uri_from_name(user);
3727 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3729 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3730 if(sipe_strcase_equal(acknowledged,"false")){
3731 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
3732 if (!purple_find_buddy(sip->account, uri)) {
3733 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3736 hdr = g_strdup_printf(
3737 "Contact: %s\r\n"
3738 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3740 body = g_strdup_printf(
3741 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3742 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3743 "</setSubscribers>", user);
3745 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3746 g_free(body);
3747 g_free(hdr);
3749 g_free(display_name);
3750 g_free(uri);
3753 g_free(contact);
3754 xmlnode_free(xml);
3756 /* Publish initial state if not yet.
3757 * Assuming this happens on initial responce to subscription to roaming-self
3758 * so we've already updated our roaming data in full.
3759 * Only for 2007+
3761 if (!sip->initial_state_published) {
3762 send_publish_category_initial(sip);
3763 sip->initial_state_published = TRUE;
3764 /* dalayed run */
3765 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3766 do_update_status = FALSE;
3767 } else if (aggreg_avail) {
3769 g_free(sip->status);
3770 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3771 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3772 } else {
3773 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3777 if (do_update_status) {
3778 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
3779 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3782 g_free(to);
3785 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3787 gchar *to = sip_uri_self(sip);
3788 gchar *tmp = get_contact(sip);
3789 gchar *hdr = g_strdup_printf(
3790 "Event: vnd-microsoft-roaming-ACL\r\n"
3791 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3792 "Supported: com.microsoft.autoextend\r\n"
3793 "Supported: ms-benotify\r\n"
3794 "Proxy-Require: ms-benotify\r\n"
3795 "Supported: ms-piggyback-first-notify\r\n"
3796 "Contact: %s\r\n", tmp);
3797 g_free(tmp);
3799 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3800 g_free(to);
3801 g_free(hdr);
3805 * To request for presence information about the user, access level settings that have already been configured by the user
3806 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3807 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3810 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3812 gchar *to = sip_uri_self(sip);
3813 gchar *tmp = get_contact(sip);
3814 gchar *hdr = g_strdup_printf(
3815 "Event: vnd-microsoft-roaming-self\r\n"
3816 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3817 "Supported: ms-benotify\r\n"
3818 "Proxy-Require: ms-benotify\r\n"
3819 "Supported: ms-piggyback-first-notify\r\n"
3820 "Contact: %s\r\n"
3821 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3823 gchar *body=g_strdup(
3824 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3825 "<roaming type=\"categories\"/>"
3826 "<roaming type=\"containers\"/>"
3827 "<roaming type=\"subscribers\"/></roamingList>");
3829 g_free(tmp);
3830 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3831 g_free(body);
3832 g_free(to);
3833 g_free(hdr);
3837 * For 2005 version
3839 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3841 gchar *to = sip_uri_self(sip);
3842 gchar *tmp = get_contact(sip);
3843 gchar *hdr = g_strdup_printf(
3844 "Event: vnd-microsoft-provisioning\r\n"
3845 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3846 "Supported: com.microsoft.autoextend\r\n"
3847 "Supported: ms-benotify\r\n"
3848 "Proxy-Require: ms-benotify\r\n"
3849 "Supported: ms-piggyback-first-notify\r\n"
3850 "Expires: 0\r\n"
3851 "Contact: %s\r\n", tmp);
3853 g_free(tmp);
3854 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3855 g_free(to);
3856 g_free(hdr);
3859 /** Subscription for provisioning information to help with initial
3860 * configuration. This subscription is a one-time query (denoted by the Expires header,
3861 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3862 * configuration, meeting policies, and policy settings that Communicator must enforce.
3863 * TODO: for what we need this information.
3866 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3868 gchar *to = sip_uri_self(sip);
3869 gchar *tmp = get_contact(sip);
3870 gchar *hdr = g_strdup_printf(
3871 "Event: vnd-microsoft-provisioning-v2\r\n"
3872 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3873 "Supported: com.microsoft.autoextend\r\n"
3874 "Supported: ms-benotify\r\n"
3875 "Proxy-Require: ms-benotify\r\n"
3876 "Supported: ms-piggyback-first-notify\r\n"
3877 "Expires: 0\r\n"
3878 "Contact: %s\r\n"
3879 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3880 gchar *body = g_strdup(
3881 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3882 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3883 "<provisioningGroup name=\"ucPolicy\"/>"
3884 "</provisioningGroupList>");
3886 g_free(tmp);
3887 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3888 g_free(body);
3889 g_free(to);
3890 g_free(hdr);
3893 static void
3894 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3895 gpointer value, gpointer user_data)
3897 struct sip_subscription *subscription = value;
3898 struct sip_dialog *dialog = &subscription->dialog;
3899 struct sipe_account_data *sip = user_data;
3900 gchar *tmp = get_contact(sip);
3901 gchar *hdr = g_strdup_printf(
3902 "Event: %s\r\n"
3903 "Expires: 0\r\n"
3904 "Contact: %s\r\n", subscription->event, tmp);
3905 g_free(tmp);
3907 /* Rate limit to max. 25 requests per seconds */
3908 g_usleep(1000000 / 25);
3910 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3911 g_free(hdr);
3914 /* IM Session (INVITE and MESSAGE methods) */
3916 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3917 static gchar *
3918 get_end_points (struct sipe_account_data *sip,
3919 struct sip_session *session)
3921 gchar *res;
3923 if (session == NULL) {
3924 return NULL;
3927 res = g_strdup_printf("<sip:%s>", sip->username);
3929 SIPE_DIALOG_FOREACH {
3930 gchar *tmp = res;
3931 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3932 g_free(tmp);
3934 if (dialog->theirepid) {
3935 tmp = res;
3936 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3937 g_free(tmp);
3939 } SIPE_DIALOG_FOREACH_END;
3941 return res;
3944 static gboolean
3945 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3946 struct sipmsg *msg,
3947 SIPE_UNUSED_PARAMETER struct transaction *trans)
3949 gboolean ret = TRUE;
3951 if (msg->response != 200) {
3952 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
3953 return FALSE;
3956 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
3958 return ret;
3962 * Asks UA/proxy about its capabilities.
3964 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3966 gchar *to = sip_uri(who);
3967 gchar *contact = get_contact(sip);
3968 gchar *request = g_strdup_printf(
3969 "Accept: application/sdp\r\n"
3970 "Contact: %s\r\n", contact);
3971 g_free(contact);
3973 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3975 g_free(to);
3976 g_free(request);
3979 static void
3980 sipe_notify_user(struct sipe_account_data *sip,
3981 struct sip_session *session,
3982 PurpleMessageFlags flags,
3983 const gchar *message)
3985 PurpleConversation *conv;
3987 if (!session->conv) {
3988 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3989 } else {
3990 conv = session->conv;
3992 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3995 void
3996 sipe_present_info(struct sipe_account_data *sip,
3997 struct sip_session *session,
3998 const gchar *message)
4000 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4003 static void
4004 sipe_present_err(struct sipe_account_data *sip,
4005 struct sip_session *session,
4006 const gchar *message)
4008 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4011 void
4012 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4013 struct sip_session *session,
4014 int sip_error,
4015 int sip_warning,
4016 const gchar *who,
4017 const gchar *message)
4019 char *msg, *msg_tmp, *msg_tmp2;
4020 const char *label;
4022 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4023 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4024 g_free(msg_tmp);
4025 /* Service unavailable; Server Internal Error; Server Time-out */
4026 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4027 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4028 g_free(msg);
4029 msg = NULL;
4030 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4031 label = _("This message was not delivered to %s because the service is not available");
4032 } else if (sip_error == 486) { /* Busy Here */
4033 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4034 } else if (sip_error == 415) { /* Unsupported media type */
4035 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4036 } else {
4037 label = _("This message was not delivered to %s because one or more recipients are offline");
4040 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4041 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4042 msg ? ":" : "",
4043 msg ? msg : "");
4044 sipe_present_err(sip, session, msg_tmp);
4045 g_free(msg_tmp2);
4046 g_free(msg_tmp);
4047 g_free(msg);
4051 static gboolean
4052 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4053 SIPE_UNUSED_PARAMETER struct transaction *trans)
4055 gboolean ret = TRUE;
4056 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4057 struct sip_session *session = sipe_session_find_im(sip, with);
4058 struct sip_dialog *dialog;
4059 gchar *cseq;
4060 char *key;
4061 struct queued_message *message;
4063 if (!session) {
4064 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4065 g_free(with);
4066 return FALSE;
4069 dialog = sipe_dialog_find(session, with);
4070 if (!dialog) {
4071 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4072 g_free(with);
4073 return FALSE;
4076 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4077 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4078 g_free(cseq);
4079 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4081 if (msg->response >= 400) {
4082 PurpleBuddy *pbuddy;
4083 const char *alias = with;
4084 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4085 int warning = -1;
4087 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4089 if (warn_hdr) {
4090 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4091 if (parts[0]) {
4092 warning = atoi(parts[0]);
4094 g_strfreev(parts);
4097 /* cancel file transfer as rejected by server */
4098 if (msg->response == 606 && /* Not acceptable all. */
4099 warning == 309 && /* Message contents not allowed by policy */
4100 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4102 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4103 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4104 sipe_utils_nameval_free(parsed_body);
4107 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4108 alias = purple_buddy_get_alias(pbuddy);
4111 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4113 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4114 if (msg->response == 408 || /* Request timeout */
4115 msg->response == 480 || /* Temporarily Unavailable */
4116 msg->response == 481) { /* Call/Transaction Does Not Exist */
4117 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4118 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4121 ret = FALSE;
4122 } else {
4123 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4124 if (message_id) {
4125 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4126 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4127 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4130 g_hash_table_remove(session->unconfirmed_messages, key);
4131 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4132 key, g_hash_table_size(session->unconfirmed_messages));
4135 g_free(key);
4136 g_free(with);
4138 if (ret) sipe_im_process_queue(sip, session);
4139 return ret;
4142 static gboolean
4143 sipe_is_election_finished(struct sip_session *session);
4145 static void
4146 sipe_election_result(struct sipe_account_data *sip,
4147 void *sess);
4149 static gboolean
4150 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4151 SIPE_UNUSED_PARAMETER struct transaction *trans)
4153 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4154 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4155 struct sip_dialog *dialog;
4156 struct sip_session *session;
4158 session = sipe_session_find_chat_by_callid(sip, callid);
4159 if (!session) {
4160 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4161 return FALSE;
4164 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4165 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4166 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4167 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4169 if (xn_request_rm_response) {
4170 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4171 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4173 dialog = sipe_dialog_find(session, with);
4174 if (!dialog) {
4175 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4176 xmlnode_free(xn_action);
4177 return FALSE;
4180 if (allow && !g_strcasecmp(allow, "true")) {
4181 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4182 dialog->election_vote = 1;
4183 } else if (allow && !g_strcasecmp(allow, "false")) {
4184 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4185 dialog->election_vote = -1;
4188 if (sipe_is_election_finished(session)) {
4189 sipe_election_result(sip, session);
4192 } else if (xn_set_rm_response) {
4195 xmlnode_free(xn_action);
4199 return TRUE;
4202 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4204 gchar *hdr;
4205 gchar *tmp;
4206 char *msgtext = NULL;
4207 const gchar *msgr = "";
4208 gchar *tmp2 = NULL;
4210 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4211 char *msgformat;
4212 gchar *msgr_value;
4214 sipe_parse_html(msg, &msgformat, &msgtext);
4215 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4217 msgr_value = sipmsg_get_msgr_string(msgformat);
4218 g_free(msgformat);
4219 if (msgr_value) {
4220 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4221 g_free(msgr_value);
4223 } else {
4224 msgtext = g_strdup(msg);
4227 tmp = get_contact(sip);
4228 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4229 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4230 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4231 if (content_type == NULL)
4232 content_type = "text/plain";
4234 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4235 g_free(tmp);
4236 g_free(tmp2);
4238 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4239 g_free(msgtext);
4240 g_free(hdr);
4244 void
4245 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4247 GSList *entry2 = session->outgoing_message_queue;
4248 while (entry2) {
4249 struct queued_message *msg = entry2->data;
4251 /* for multiparty chat or conference */
4252 if (session->is_multiparty || session->focus_uri) {
4253 gchar *who = sip_uri_self(sip);
4254 serv_got_chat_in(sip->gc, session->chat_id, who,
4255 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4256 g_free(who);
4259 SIPE_DIALOG_FOREACH {
4260 char *key;
4261 struct queued_message *message;
4263 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4265 message = g_new0(struct queued_message,1);
4266 message->body = g_strdup(msg->body);
4267 if (msg->content_type != NULL)
4268 message->content_type = g_strdup(msg->content_type);
4270 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4271 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4272 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4273 key, g_hash_table_size(session->unconfirmed_messages));
4274 g_free(key);
4276 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4277 } SIPE_DIALOG_FOREACH_END;
4279 entry2 = sipe_session_dequeue_message(session);
4283 static void
4284 sipe_refer_notify(struct sipe_account_data *sip,
4285 struct sip_session *session,
4286 const gchar *who,
4287 int status,
4288 const gchar *desc)
4290 gchar *hdr;
4291 gchar *body;
4292 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4294 hdr = g_strdup_printf(
4295 "Event: refer\r\n"
4296 "Subscription-State: %s\r\n"
4297 "Content-Type: message/sipfrag\r\n",
4298 status >= 200 ? "terminated" : "active");
4300 body = g_strdup_printf(
4301 "SIP/2.0 %d %s\r\n",
4302 status, desc);
4304 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4306 g_free(hdr);
4307 g_free(body);
4310 static gboolean
4311 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4313 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4314 struct sip_session *session;
4315 struct sip_dialog *dialog;
4316 char *cseq;
4317 char *key;
4318 struct queued_message *message;
4319 struct sipmsg *request_msg = trans->msg;
4321 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4322 gchar *referred_by;
4324 session = sipe_session_find_chat_by_callid(sip, callid);
4325 if (!session) {
4326 session = sipe_session_find_im(sip, with);
4328 if (!session) {
4329 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4330 g_free(with);
4331 return FALSE;
4334 dialog = sipe_dialog_find(session, with);
4335 if (!dialog) {
4336 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4337 g_free(with);
4338 return FALSE;
4341 sipe_dialog_parse(dialog, msg, TRUE);
4343 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4344 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4345 g_free(cseq);
4346 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4348 if (msg->response != 200) {
4349 PurpleBuddy *pbuddy;
4350 const char *alias = with;
4351 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4352 int warning = -1;
4354 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4356 if (warn_hdr) {
4357 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4358 if (parts[0]) {
4359 warning = atoi(parts[0]);
4361 g_strfreev(parts);
4364 /* cancel file transfer as rejected by server */
4365 if (msg->response == 606 && /* Not acceptable all. */
4366 warning == 309 && /* Message contents not allowed by policy */
4367 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4369 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4370 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4371 sipe_utils_nameval_free(parsed_body);
4374 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4375 alias = purple_buddy_get_alias(pbuddy);
4378 if (message) {
4379 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4380 } else {
4381 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4382 sipe_present_err(sip, session, tmp_msg);
4383 g_free(tmp_msg);
4386 sipe_dialog_remove(session, with);
4388 g_free(key);
4389 g_free(with);
4390 return FALSE;
4393 dialog->cseq = 0;
4394 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4395 dialog->outgoing_invite = NULL;
4396 dialog->is_established = TRUE;
4398 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4399 if (referred_by) {
4400 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4401 g_free(referred_by);
4404 /* add user to chat if it is a multiparty session */
4405 if (session->is_multiparty) {
4406 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4407 with, NULL,
4408 PURPLE_CBFLAGS_NONE, TRUE);
4411 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4412 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4413 sipe_session_dequeue_message(session);
4416 sipe_im_process_queue(sip, session);
4418 g_hash_table_remove(session->unconfirmed_messages, key);
4419 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4420 key, g_hash_table_size(session->unconfirmed_messages));
4422 g_free(key);
4423 g_free(with);
4424 return TRUE;
4428 void
4429 sipe_invite(struct sipe_account_data *sip,
4430 struct sip_session *session,
4431 const gchar *who,
4432 const gchar *msg_body,
4433 const gchar *msg_content_type,
4434 const gchar *referred_by,
4435 const gboolean is_triggered)
4437 gchar *hdr;
4438 gchar *to;
4439 gchar *contact;
4440 gchar *body;
4441 gchar *self;
4442 char *ms_text_format = NULL;
4443 gchar *roster_manager;
4444 gchar *end_points;
4445 gchar *referred_by_str;
4446 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4448 if (dialog && dialog->is_established) {
4449 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4450 return;
4453 if (!dialog) {
4454 dialog = sipe_dialog_add(session);
4455 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4456 dialog->with = g_strdup(who);
4459 if (!(dialog->ourtag)) {
4460 dialog->ourtag = gentag();
4463 to = sip_uri(who);
4465 if (msg_body) {
4466 char *msgtext = NULL;
4467 char *base64_msg;
4468 const gchar *msgr = "";
4469 char *key;
4470 struct queued_message *message;
4471 gchar *tmp = NULL;
4473 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4474 char *msgformat;
4475 gchar *msgr_value;
4477 sipe_parse_html(msg_body, &msgformat, &msgtext);
4478 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4480 msgr_value = sipmsg_get_msgr_string(msgformat);
4481 g_free(msgformat);
4482 if (msgr_value) {
4483 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4484 g_free(msgr_value);
4486 } else {
4487 msgtext = g_strdup(msg_body);
4490 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4491 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4492 msg_content_type ? msg_content_type : "text/plain",
4493 msgr,
4494 base64_msg);
4495 g_free(msgtext);
4496 g_free(tmp);
4497 g_free(base64_msg);
4499 message = g_new0(struct queued_message,1);
4500 message->body = g_strdup(msg_body);
4501 if (msg_content_type != NULL)
4502 message->content_type = g_strdup(msg_content_type);
4504 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4505 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4506 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4507 key, g_hash_table_size(session->unconfirmed_messages));
4508 g_free(key);
4511 contact = get_contact(sip);
4512 end_points = get_end_points(sip, session);
4513 self = sip_uri_self(sip);
4514 roster_manager = g_strdup_printf(
4515 "Roster-Manager: %s\r\n"
4516 "EndPoints: %s\r\n",
4517 self,
4518 end_points);
4519 referred_by_str = referred_by ?
4520 g_strdup_printf(
4521 "Referred-By: %s\r\n",
4522 referred_by)
4523 : g_strdup("");
4524 hdr = g_strdup_printf(
4525 "Supported: ms-sender\r\n"
4526 "%s"
4527 "%s"
4528 "%s"
4529 "%s"
4530 "Contact: %s\r\n%s"
4531 "Content-Type: application/sdp\r\n",
4532 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4533 referred_by_str,
4534 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4535 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4536 contact,
4537 ms_text_format ? ms_text_format : "");
4538 g_free(ms_text_format);
4539 g_free(self);
4541 body = g_strdup_printf(
4542 "v=0\r\n"
4543 "o=- 0 0 IN IP4 %s\r\n"
4544 "s=session\r\n"
4545 "c=IN IP4 %s\r\n"
4546 "t=0 0\r\n"
4547 "m=%s %d sip null\r\n"
4548 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4549 purple_network_get_my_ip(-1),
4550 purple_network_get_my_ip(-1),
4551 sip->ocs2007 ? "message" : "x-ms-message",
4552 sip->realport);
4554 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4555 to, to, hdr, body, dialog, process_invite_response);
4557 g_free(to);
4558 g_free(roster_manager);
4559 g_free(end_points);
4560 g_free(referred_by_str);
4561 g_free(body);
4562 g_free(hdr);
4563 g_free(contact);
4566 static void
4567 sipe_refer(struct sipe_account_data *sip,
4568 struct sip_session *session,
4569 const gchar *who)
4571 gchar *hdr;
4572 gchar *contact;
4573 gchar *epid = get_epid(sip);
4574 struct sip_dialog *dialog = sipe_dialog_find(session,
4575 session->roster_manager);
4576 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4578 contact = get_contact(sip);
4579 hdr = g_strdup_printf(
4580 "Contact: %s\r\n"
4581 "Refer-to: <%s>\r\n"
4582 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4583 "Require: com.microsoft.rtc-multiparty\r\n",
4584 contact,
4585 who,
4586 sip->username,
4587 ourtag ? ";tag=" : "",
4588 ourtag ? ourtag : "",
4589 epid);
4590 g_free(epid);
4592 send_sip_request(sip->gc, "REFER",
4593 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4595 g_free(hdr);
4596 g_free(contact);
4599 static void
4600 sipe_send_election_request_rm(struct sipe_account_data *sip,
4601 struct sip_dialog *dialog,
4602 int bid)
4604 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4606 gchar *body = g_strdup_printf(
4607 "<?xml version=\"1.0\"?>\r\n"
4608 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4609 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4610 sip->username, bid);
4612 send_sip_request(sip->gc, "INFO",
4613 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4615 g_free(body);
4618 static void
4619 sipe_send_election_set_rm(struct sipe_account_data *sip,
4620 struct sip_dialog *dialog)
4622 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4624 gchar *body = g_strdup_printf(
4625 "<?xml version=\"1.0\"?>\r\n"
4626 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4627 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4628 sip->username);
4630 send_sip_request(sip->gc, "INFO",
4631 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4633 g_free(body);
4636 static void
4637 sipe_session_close(struct sipe_account_data *sip,
4638 struct sip_session * session)
4640 if (session && session->focus_uri) {
4641 sipe_conf_immcu_closed(sip, session);
4642 conf_session_close(sip, session);
4645 if (session) {
4646 SIPE_DIALOG_FOREACH {
4647 /* @TODO slow down BYE message sending rate */
4648 /* @see single subscription code */
4649 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4650 } SIPE_DIALOG_FOREACH_END;
4652 sipe_session_remove(sip, session);
4656 static void
4657 sipe_session_close_all(struct sipe_account_data *sip)
4659 GSList *entry;
4660 while ((entry = sip->sessions) != NULL) {
4661 sipe_session_close(sip, entry->data);
4665 static void
4666 sipe_convo_closed(PurpleConnection * gc, const char *who)
4668 struct sipe_account_data *sip = gc->proto_data;
4670 SIPE_DEBUG_INFO("conversation with %s closed", who);
4671 sipe_session_close(sip, sipe_session_find_im(sip, who));
4674 static void
4675 sipe_chat_invite(PurpleConnection *gc, int id,
4676 SIPE_UNUSED_PARAMETER const char *message,
4677 const char *name)
4679 sipe_chat_create(gc->proto_data, id, name);
4682 static void
4683 sipe_chat_leave (PurpleConnection *gc, int id)
4685 struct sipe_account_data *sip = gc->proto_data;
4686 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4688 sipe_session_close(sip, session);
4691 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4692 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4694 struct sipe_account_data *sip = gc->proto_data;
4695 struct sip_session *session;
4696 struct sip_dialog *dialog;
4697 gchar *uri = sip_uri(who);
4699 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4701 session = sipe_session_find_or_add_im(sip, uri);
4702 dialog = sipe_dialog_find(session, uri);
4704 // Queue the message
4705 sipe_session_enqueue_message(session, what, NULL);
4707 if (dialog && !dialog->outgoing_invite) {
4708 sipe_im_process_queue(sip, session);
4709 } else if (!dialog || !dialog->outgoing_invite) {
4710 // Need to send the INVITE to get the outgoing dialog setup
4711 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4714 g_free(uri);
4715 return 1;
4718 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4719 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4721 struct sipe_account_data *sip = gc->proto_data;
4722 struct sip_session *session;
4724 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
4726 session = sipe_session_find_chat_by_id(sip, id);
4728 // Queue the message
4729 if (session && session->dialogs) {
4730 sipe_session_enqueue_message(session,what,NULL);
4731 sipe_im_process_queue(sip, session);
4732 } else if (sip) {
4733 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4734 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4736 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
4737 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
4739 if (sip->ocs2007) {
4740 struct sip_session *session = sipe_session_add_chat(sip);
4742 session->is_multiparty = FALSE;
4743 session->focus_uri = g_strdup(proto_chat_id);
4744 sipe_session_enqueue_message(session, what, NULL);
4745 sipe_invite_conf_focus(sip, session);
4749 return 1;
4752 /* End IM Session (INVITE and MESSAGE methods) */
4754 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4756 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4757 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4758 gchar *from;
4759 struct sip_session *session;
4761 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
4763 /* Call Control protocol */
4764 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4766 process_incoming_info_csta(sip, msg);
4767 return;
4770 from = parse_from(sipmsg_find_header(msg, "From"));
4771 session = sipe_session_find_chat_by_callid(sip, callid);
4772 if (!session) {
4773 session = sipe_session_find_im(sip, from);
4775 if (!session) {
4776 g_free(from);
4777 return;
4780 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4782 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4783 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4784 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4786 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4788 if (xn_request_rm) {
4789 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4790 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4791 gchar *body = g_strdup_printf(
4792 "<?xml version=\"1.0\"?>\r\n"
4793 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4794 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4795 sip->username,
4796 session->bid < bid ? "true" : "false");
4797 send_sip_response(sip->gc, msg, 200, "OK", body);
4798 g_free(body);
4799 } else if (xn_set_rm) {
4800 gchar *body;
4801 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4802 g_free(session->roster_manager);
4803 session->roster_manager = g_strdup(rm);
4805 body = g_strdup_printf(
4806 "<?xml version=\"1.0\"?>\r\n"
4807 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4808 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4809 sip->username);
4810 send_sip_response(sip->gc, msg, 200, "OK", body);
4811 g_free(body);
4813 xmlnode_free(xn_action);
4816 else
4818 /* looks like purple lacks typing notification for chat */
4819 if (!session->is_multiparty && !session->focus_uri) {
4820 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4821 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4822 "status");
4823 if (sipe_strequal(status, "type")) {
4824 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4825 } else if (sipe_strequal(status, "idle")) {
4826 serv_got_typing_stopped(sip->gc, from);
4828 xmlnode_free(xn_keyboard_activity);
4831 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4833 g_free(from);
4836 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4838 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4839 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4840 struct sip_session *session;
4841 struct sip_dialog *dialog;
4843 /* collect dialog identification
4844 * we need callid, ourtag and theirtag to unambiguously identify dialog
4846 /* take data before 'msg' will be modified by send_sip_response */
4847 dialog = g_new0(struct sip_dialog, 1);
4848 dialog->callid = g_strdup(callid);
4849 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4850 dialog->with = g_strdup(from);
4851 sipe_dialog_parse(dialog, msg, FALSE);
4853 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4855 session = sipe_session_find_chat_by_callid(sip, callid);
4856 if (!session) {
4857 session = sipe_session_find_im(sip, from);
4859 if (!session) {
4860 sipe_dialog_free(dialog);
4861 g_free(from);
4862 return;
4865 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4866 g_free(session->roster_manager);
4867 session->roster_manager = NULL;
4870 /* This what BYE is essentially for - terminating dialog */
4871 sipe_dialog_remove_3(session, dialog);
4872 sipe_dialog_free(dialog);
4873 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4874 sipe_conf_immcu_closed(sip, session);
4875 } else if (session->is_multiparty) {
4876 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4879 g_free(from);
4882 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4884 gchar *self = sip_uri_self(sip);
4885 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4886 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4887 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4888 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4889 struct sip_session *session;
4890 struct sip_dialog *dialog;
4892 session = sipe_session_find_chat_by_callid(sip, callid);
4893 dialog = sipe_dialog_find(session, from);
4895 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
4896 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4897 } else {
4898 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4900 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4903 g_free(self);
4904 g_free(from);
4905 g_free(refer_to);
4906 g_free(referred_by);
4909 static unsigned int
4910 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4912 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4913 struct sip_session *session;
4914 struct sip_dialog *dialog;
4916 if (state == PURPLE_NOT_TYPING)
4917 return 0;
4919 session = sipe_session_find_im(sip, who);
4920 dialog = sipe_dialog_find(session, who);
4922 if (session && dialog && dialog->is_established) {
4923 send_sip_request(gc, "INFO", who, who,
4924 "Content-Type: application/xml\r\n",
4925 SIPE_SEND_TYPING, dialog, NULL);
4927 return SIPE_TYPING_SEND_TIMEOUT;
4930 static gboolean resend_timeout(struct sipe_account_data *sip)
4932 GSList *tmp = sip->transactions;
4933 time_t currtime = time(NULL);
4934 while (tmp) {
4935 struct transaction *trans = tmp->data;
4936 tmp = tmp->next;
4937 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
4938 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4939 /* TODO 408 */
4940 } else {
4941 if ((currtime - trans->time > 2) && trans->retries == 0) {
4942 trans->retries++;
4943 sendout_sipmsg(sip, trans->msg);
4947 return TRUE;
4950 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4951 SIPE_UNUSED_PARAMETER void *unused)
4953 /* register again when security token expires */
4954 /* we have to start a new authentication as the security token
4955 * is almost expired by sending a not signed REGISTER message */
4956 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
4957 sipe_auth_free(&sip->registrar);
4958 sipe_auth_free(&sip->proxy);
4959 sip->registerstatus = 0;
4960 do_register(sip);
4961 sip->reauthenticate_set = FALSE;
4964 static gboolean
4965 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4966 struct sipmsg *msg,
4967 GSList *parsed_body)
4969 gboolean found = FALSE;
4971 if (parsed_body) {
4972 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4974 if (sipe_strequal(invitation_command, "INVITE")) {
4975 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4976 found = TRUE;
4977 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4978 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4979 found = TRUE;
4980 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4981 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4982 found = TRUE;
4985 return found;
4988 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4990 gchar *from;
4991 const gchar *contenttype;
4992 gboolean found = FALSE;
4994 from = parse_from(sipmsg_find_header(msg, "From"));
4996 if (!from) return;
4998 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5000 contenttype = sipmsg_find_header(msg, "Content-Type");
5001 if (g_str_has_prefix(contenttype, "text/plain")
5002 || g_str_has_prefix(contenttype, "text/html")
5003 || g_str_has_prefix(contenttype, "multipart/related")
5004 || g_str_has_prefix(contenttype, "multipart/alternative"))
5006 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5007 gchar *html = get_html_message(contenttype, msg->body);
5009 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5010 if (!session) {
5011 session = sipe_session_find_im(sip, from);
5014 if (session && session->focus_uri) { /* a conference */
5015 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5016 gchar *sender = parse_from(tmp);
5017 g_free(tmp);
5018 serv_got_chat_in(sip->gc, session->chat_id, sender,
5019 PURPLE_MESSAGE_RECV, html, time(NULL));
5020 g_free(sender);
5021 } else if (session && session->is_multiparty) { /* a multiparty chat */
5022 serv_got_chat_in(sip->gc, session->chat_id, from,
5023 PURPLE_MESSAGE_RECV, html, time(NULL));
5024 } else {
5025 serv_got_im(sip->gc, from, html, 0, time(NULL));
5027 g_free(html);
5028 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5029 found = TRUE;
5031 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5032 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5033 xmlnode *state;
5034 gchar *statedata;
5036 if (!isc) {
5037 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5038 g_free(from);
5039 return;
5042 state = xmlnode_get_child(isc, "state");
5044 if (!state) {
5045 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5046 xmlnode_free(isc);
5047 g_free(from);
5048 return;
5051 statedata = xmlnode_get_data(state);
5052 if (statedata) {
5053 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5054 else serv_got_typing_stopped(sip->gc, from);
5056 g_free(statedata);
5058 xmlnode_free(isc);
5059 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5060 found = TRUE;
5061 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5062 GSList *body = sipe_ft_parse_msg_body(msg->body);
5063 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5064 sipe_utils_nameval_free(body);
5065 if (found) {
5066 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5069 if (!found) {
5070 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5071 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5072 if (!session) {
5073 session = sipe_session_find_im(sip, from);
5075 if (session) {
5076 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5077 from);
5078 sipe_present_err(sip, session, errmsg);
5079 g_free(errmsg);
5082 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5083 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5085 g_free(from);
5088 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5090 gchar *body;
5091 gchar *newTag;
5092 const gchar *oldHeader;
5093 gchar *newHeader;
5094 gboolean is_multiparty = FALSE;
5095 gboolean is_triggered = FALSE;
5096 gboolean was_multiparty = TRUE;
5097 gboolean just_joined = FALSE;
5098 gchar *from;
5099 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5100 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5101 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5102 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5103 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5104 GSList *end_points = NULL;
5105 char *tmp = NULL;
5106 struct sip_session *session;
5107 const gchar *ms_text_format;
5109 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5110 g_free(tmp);
5112 /* Invitation to join conference */
5113 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5114 process_incoming_invite_conf(sip, msg);
5115 return;
5118 /* Only accept text invitations */
5119 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5120 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5121 return;
5124 // TODO There *must* be a better way to clean up the To header to add a tag...
5125 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5126 oldHeader = sipmsg_find_header(msg, "To");
5127 newTag = gentag();
5128 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5129 sipmsg_remove_header_now(msg, "To");
5130 sipmsg_add_header_now(msg, "To", newHeader);
5131 g_free(newHeader);
5133 if (end_points_hdr) {
5134 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5136 if (g_slist_length(end_points) > 2) {
5137 is_multiparty = TRUE;
5140 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5141 is_triggered = TRUE;
5142 is_multiparty = TRUE;
5145 session = sipe_session_find_chat_by_callid(sip, callid);
5146 /* Convert to multiparty */
5147 if (session && is_multiparty && !session->is_multiparty) {
5148 g_free(session->with);
5149 session->with = NULL;
5150 was_multiparty = FALSE;
5151 session->is_multiparty = TRUE;
5152 session->chat_id = rand();
5155 if (!session && is_multiparty) {
5156 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5158 /* IM session */
5159 from = parse_from(sipmsg_find_header(msg, "From"));
5160 if (!session) {
5161 session = sipe_session_find_or_add_im(sip, from);
5164 if (session) {
5165 g_free(session->callid);
5166 session->callid = g_strdup(callid);
5168 session->is_multiparty = is_multiparty;
5169 if (roster_manager) {
5170 session->roster_manager = g_strdup(roster_manager);
5174 if (is_multiparty && end_points) {
5175 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5176 GSList *entry = end_points;
5177 while (entry) {
5178 struct sip_dialog *dialog;
5179 struct sipendpoint *end_point = entry->data;
5180 entry = entry->next;
5182 if (!g_strcasecmp(from, end_point->contact) ||
5183 !g_strcasecmp(to, end_point->contact))
5184 continue;
5186 dialog = sipe_dialog_find(session, end_point->contact);
5187 if (dialog) {
5188 g_free(dialog->theirepid);
5189 dialog->theirepid = end_point->epid;
5190 end_point->epid = NULL;
5191 } else {
5192 dialog = sipe_dialog_add(session);
5194 dialog->callid = g_strdup(session->callid);
5195 dialog->with = end_point->contact;
5196 end_point->contact = NULL;
5197 dialog->theirepid = end_point->epid;
5198 end_point->epid = NULL;
5200 just_joined = TRUE;
5202 /* send triggered INVITE */
5203 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5206 g_free(to);
5209 if (end_points) {
5210 GSList *entry = end_points;
5211 while (entry) {
5212 struct sipendpoint *end_point = entry->data;
5213 entry = entry->next;
5214 g_free(end_point->contact);
5215 g_free(end_point->epid);
5216 g_free(end_point);
5218 g_slist_free(end_points);
5221 if (session) {
5222 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5223 if (dialog) {
5224 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5225 sipe_dialog_parse_routes(dialog, msg, FALSE);
5226 } else {
5227 dialog = sipe_dialog_add(session);
5229 dialog->callid = g_strdup(session->callid);
5230 dialog->with = g_strdup(from);
5231 sipe_dialog_parse(dialog, msg, FALSE);
5233 if (!dialog->ourtag) {
5234 dialog->ourtag = newTag;
5235 newTag = NULL;
5238 just_joined = TRUE;
5240 } else {
5241 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5243 g_free(newTag);
5245 if (is_multiparty && !session->conv) {
5246 gchar *chat_title = sipe_chat_get_name(callid);
5247 gchar *self = sip_uri_self(sip);
5248 /* create prpl chat */
5249 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5250 session->chat_title = g_strdup(chat_title);
5251 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5252 /* add self */
5253 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5254 self, NULL,
5255 PURPLE_CBFLAGS_NONE, FALSE);
5256 g_free(chat_title);
5257 g_free(self);
5260 if (is_multiparty && !was_multiparty) {
5261 /* add current IM counterparty to chat */
5262 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5263 sipe_dialog_first(session)->with, NULL,
5264 PURPLE_CBFLAGS_NONE, FALSE);
5267 /* add inviting party to chat */
5268 if (just_joined && session->conv) {
5269 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5270 from, NULL,
5271 PURPLE_CBFLAGS_NONE, TRUE);
5274 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5276 /* This used only in 2005 official client, not 2007 or Reuters.
5277 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5278 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5280 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5281 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5282 if (is_multiparty ||
5283 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5285 if (ms_text_format) {
5286 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5288 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5289 if (tmp) {
5290 gsize len;
5291 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5293 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5295 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5296 sipe_utils_nameval_free(parsed_body);
5297 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5299 g_free(tmp);
5301 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5303 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5304 gchar *html = get_html_message(ms_text_format, NULL);
5305 if (html) {
5306 if (is_multiparty) {
5307 serv_got_chat_in(sip->gc, session->chat_id, from,
5308 PURPLE_MESSAGE_RECV, html, time(NULL));
5309 } else {
5310 serv_got_im(sip->gc, from, html, 0, time(NULL));
5312 g_free(html);
5313 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5319 g_free(from);
5321 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5322 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5323 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5325 body = g_strdup_printf(
5326 "v=0\r\n"
5327 "o=- 0 0 IN IP4 %s\r\n"
5328 "s=session\r\n"
5329 "c=IN IP4 %s\r\n"
5330 "t=0 0\r\n"
5331 "m=%s %d sip sip:%s\r\n"
5332 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5333 purple_network_get_my_ip(-1),
5334 purple_network_get_my_ip(-1),
5335 sip->ocs2007 ? "message" : "x-ms-message",
5336 sip->realport,
5337 sip->username);
5338 send_sip_response(sip->gc, msg, 200, "OK", body);
5339 g_free(body);
5342 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5344 gchar *body;
5346 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5347 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5348 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5350 body = g_strdup_printf(
5351 "v=0\r\n"
5352 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5353 "s=session\r\n"
5354 "c=IN IP4 0.0.0.0\r\n"
5355 "t=0 0\r\n"
5356 "m=%s %d sip sip:%s\r\n"
5357 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5358 sip->ocs2007 ? "message" : "x-ms-message",
5359 sip->realport,
5360 sip->username);
5361 send_sip_response(sip->gc, msg, 200, "OK", body);
5362 g_free(body);
5365 static const char*
5366 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5368 const char *res = "NTLM";
5369 #ifdef HAVE_KERBEROS
5370 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5371 res = "Kerberos";
5373 #else
5374 (void) sip; /* make compiler happy */
5375 #endif
5376 return res;
5379 static void sipe_connection_cleanup(struct sipe_account_data *);
5380 static void create_connection(struct sipe_account_data *, gchar *, int);
5382 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5383 SIPE_UNUSED_PARAMETER struct transaction *trans)
5385 gchar *tmp;
5386 const gchar *expires_header;
5387 int expires, i;
5388 GSList *hdr = msg->headers;
5389 struct sipnameval *elem;
5391 expires_header = sipmsg_find_header(msg, "Expires");
5392 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5393 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5395 switch (msg->response) {
5396 case 200:
5397 if (expires == 0) {
5398 sip->registerstatus = 0;
5399 } else {
5400 const gchar *contact_hdr;
5401 gchar *gruu = NULL;
5402 gchar *epid;
5403 gchar *uuid;
5404 gchar *timeout;
5405 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5406 const char *auth_scheme;
5408 if (!sip->reregister_set) {
5409 gchar *action_name = g_strdup_printf("<%s>", "registration");
5410 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5411 g_free(action_name);
5412 sip->reregister_set = TRUE;
5415 sip->registerstatus = 3;
5417 if (server_hdr && !sip->server_version) {
5418 sip->server_version = g_strdup(server_hdr);
5419 g_free(default_ua);
5420 default_ua = NULL;
5423 auth_scheme = sipe_get_auth_scheme_name(sip);
5424 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5426 if (tmp) {
5427 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5428 fill_auth(tmp, &sip->registrar);
5431 if (!sip->reauthenticate_set) {
5432 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5433 guint reauth_timeout;
5434 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5435 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5436 reauth_timeout = sip->registrar.expires - 300;
5437 } else {
5438 /* NTLM: we have to reauthenticate as our security token expires
5439 after eight hours (be five minutes early) */
5440 reauth_timeout = (8 * 3600) - 300;
5442 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5443 g_free(action_name);
5444 sip->reauthenticate_set = TRUE;
5447 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5449 epid = get_epid(sip);
5450 uuid = generateUUIDfromEPID(epid);
5451 g_free(epid);
5453 // There can be multiple Contact headers (one per location where the user is logged in) so
5454 // make sure to only get the one for this uuid
5455 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5456 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5457 if (valid_contact) {
5458 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5459 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5460 g_free(valid_contact);
5461 break;
5462 } else {
5463 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5466 g_free(uuid);
5468 g_free(sip->contact);
5469 if(gruu) {
5470 sip->contact = g_strdup_printf("<%s>", gruu);
5471 g_free(gruu);
5472 } else {
5473 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5474 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);
5476 sip->ocs2007 = FALSE;
5477 sip->batched_support = FALSE;
5479 while(hdr)
5481 elem = hdr->data;
5482 if (sipe_strcase_equal(elem->name, "Supported")) {
5483 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5484 /* We interpret this as OCS2007+ indicator */
5485 sip->ocs2007 = TRUE;
5486 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5488 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5489 sip->batched_support = TRUE;
5490 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5493 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5494 gchar **caps = g_strsplit(elem->value,",",0);
5495 i = 0;
5496 while (caps[i]) {
5497 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5498 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5499 i++;
5501 g_strfreev(caps);
5503 hdr = g_slist_next(hdr);
5506 /* rejoin open chats to be able to use them by continue to send messages */
5507 purple_conversation_foreach(sipe_rejoin_chat);
5509 /* subscriptions */
5510 if (!sip->subscribed) { //do it just once, not every re-register
5512 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5513 (GCompareFunc)g_ascii_strcasecmp)) {
5514 sipe_subscribe_roaming_contacts(sip);
5517 /* For 2007+ it does not make sence to subscribe to:
5518 * vnd-microsoft-roaming-ACL
5519 * vnd-microsoft-provisioning (not v2)
5520 * presence.wpending
5521 * These are for backward compatibility.
5523 if (sip->ocs2007)
5525 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5526 (GCompareFunc)g_ascii_strcasecmp)) {
5527 sipe_subscribe_roaming_self(sip);
5529 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5530 (GCompareFunc)g_ascii_strcasecmp)) {
5531 sipe_subscribe_roaming_provisioning_v2(sip);
5534 /* For 2005- servers */
5535 else
5537 //sipe_options_request(sip, sip->sipdomain);
5539 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5540 (GCompareFunc)g_ascii_strcasecmp)) {
5541 sipe_subscribe_roaming_acl(sip);
5543 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5544 (GCompareFunc)g_ascii_strcasecmp)) {
5545 sipe_subscribe_roaming_provisioning(sip);
5547 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5548 (GCompareFunc)g_ascii_strcasecmp)) {
5549 sipe_subscribe_presence_wpending(sip, msg);
5552 /* For 2007+ we publish our initial statuses and calendar data only after
5553 * received our existing publications in sipe_process_roaming_self()
5554 * Only in this case we know versions of current publications made
5555 * on our behalf.
5557 /* For 2005- we publish our initial statuses only after
5558 * received our existing UserInfo data in response to
5559 * self subscription.
5560 * Only in this case we won't override existing UserInfo data
5561 * set earlier or by other client on our behalf.
5565 sip->subscribed = TRUE;
5568 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5569 "timeout=", ";", NULL);
5570 if (timeout != NULL) {
5571 sscanf(timeout, "%u", &sip->keepalive_timeout);
5572 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5573 sip->keepalive_timeout);
5574 g_free(timeout);
5577 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5579 break;
5580 case 301:
5582 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5584 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5585 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5586 gchar **tmp;
5587 gchar *hostname;
5588 int port = 0;
5589 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5590 int i = 1;
5592 tmp = g_strsplit(parts[0], ":", 0);
5593 hostname = g_strdup(tmp[0]);
5594 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5595 g_strfreev(tmp);
5597 while (parts[i]) {
5598 tmp = g_strsplit(parts[i], "=", 0);
5599 if (tmp[1]) {
5600 if (g_strcasecmp("transport", tmp[0]) == 0) {
5601 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5602 transport = SIPE_TRANSPORT_TCP;
5603 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5604 transport = SIPE_TRANSPORT_UDP;
5608 g_strfreev(tmp);
5609 i++;
5611 g_strfreev(parts);
5613 /* Close old connection */
5614 sipe_connection_cleanup(sip);
5616 /* Create new connection */
5617 sip->transport = transport;
5618 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5619 hostname, port, TRANSPORT_DESCRIPTOR);
5620 create_connection(sip, hostname, port);
5622 g_free(redirect);
5624 break;
5625 case 401:
5626 if (sip->registerstatus != 2) {
5627 const char *auth_scheme;
5628 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5629 if (sip->registrar.retries > 3) {
5630 sip->gc->wants_to_die = TRUE;
5631 purple_connection_error(sip->gc, _("Authentication failed"));
5632 return TRUE;
5635 auth_scheme = sipe_get_auth_scheme_name(sip);
5636 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5638 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5639 if (!tmp) {
5640 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5641 sip->gc->wants_to_die = TRUE;
5642 purple_connection_error(sip->gc, tmp2);
5643 g_free(tmp2);
5644 return TRUE;
5646 fill_auth(tmp, &sip->registrar);
5647 sip->registerstatus = 2;
5648 if (sip->account->disconnecting) {
5649 do_register_exp(sip, 0);
5650 } else {
5651 do_register(sip);
5654 break;
5655 case 403:
5657 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5658 gchar **reason = NULL;
5659 gchar *warning;
5660 if (diagnostics != NULL) {
5661 /* Example header:
5662 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5664 reason = g_strsplit(diagnostics, "\"", 0);
5666 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5667 (reason && reason[1]) ? reason[1] : _("no reason given"));
5668 g_strfreev(reason);
5670 sip->gc->wants_to_die = TRUE;
5671 purple_connection_error(sip->gc, warning);
5672 g_free(warning);
5673 return TRUE;
5675 break;
5676 case 404:
5678 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5679 gchar *reason = NULL;
5680 gchar *warning;
5681 if (diagnostics != NULL) {
5682 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5684 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5685 diagnostics ? (reason ? reason : _("no reason given")) :
5686 _("SIP is either not enabled for the destination URI or it does not exist"));
5687 g_free(reason);
5689 sip->gc->wants_to_die = TRUE;
5690 purple_connection_error(sip->gc, warning);
5691 g_free(warning);
5692 return TRUE;
5694 break;
5695 case 503:
5696 case 504: /* Server time-out */
5698 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5699 gchar *reason = NULL;
5700 gchar *warning;
5701 if (diagnostics != NULL) {
5702 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5704 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5705 g_free(reason);
5707 sip->gc->wants_to_die = TRUE;
5708 purple_connection_error(sip->gc, warning);
5709 g_free(warning);
5710 return TRUE;
5712 break;
5714 return TRUE;
5718 * Returns 2005-style activity and Availability.
5720 * @param status Sipe statis id.
5722 static void
5723 sipe_get_act_avail_by_status_2005(const char *status,
5724 int *activity,
5725 int *availability)
5727 int avail = 300; /* online */
5728 int act = 400; /* Available */
5730 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5731 act = 100;
5732 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5733 // act = 150;
5734 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5735 act = 300;
5736 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5737 act = 400;
5738 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5739 // act = 500;
5740 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5741 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5742 act = 600;
5743 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5744 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5745 avail = 0; /* offline */
5746 act = 100;
5747 } else {
5748 act = 400; /* Available */
5751 if (activity) *activity = act;
5752 if (availability) *availability = avail;
5756 * [MS-SIP] 2.2.1
5758 * @param activity 2005 aggregated activity. Ex.: 600
5759 * @param availablity 2005 aggregated availablity. Ex.: 300
5761 static const char *
5762 sipe_get_status_by_act_avail_2005(const int activity,
5763 const int availablity,
5764 char **activity_desc)
5766 const char *status_id = NULL;
5767 const char *act = NULL;
5769 if (activity < 150) {
5770 status_id = SIPE_STATUS_ID_AWAY;
5771 } else if (activity < 200) {
5772 //status_id = SIPE_STATUS_ID_LUNCH;
5773 status_id = SIPE_STATUS_ID_AWAY;
5774 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5775 } else if (activity < 300) {
5776 //status_id = SIPE_STATUS_ID_IDLE;
5777 status_id = SIPE_STATUS_ID_AWAY;
5778 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5779 } else if (activity < 400) {
5780 status_id = SIPE_STATUS_ID_BRB;
5781 } else if (activity < 500) {
5782 status_id = SIPE_STATUS_ID_AVAILABLE;
5783 } else if (activity < 600) {
5784 //status_id = SIPE_STATUS_ID_ON_PHONE;
5785 status_id = SIPE_STATUS_ID_BUSY;
5786 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5787 } else if (activity < 700) {
5788 status_id = SIPE_STATUS_ID_BUSY;
5789 } else if (activity < 800) {
5790 status_id = SIPE_STATUS_ID_AWAY;
5791 } else {
5792 status_id = SIPE_STATUS_ID_AVAILABLE;
5795 if (availablity < 100)
5796 status_id = SIPE_STATUS_ID_OFFLINE;
5798 if (activity_desc && act) {
5799 g_free(*activity_desc);
5800 *activity_desc = g_strdup(act);
5803 return status_id;
5807 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5809 static const char*
5810 sipe_get_status_by_availability(int avail,
5811 char** activity_desc)
5813 const char *status;
5814 const char *act = NULL;
5816 if (avail < 3000) {
5817 status = SIPE_STATUS_ID_OFFLINE;
5818 } else if (avail < 4500) {
5819 status = SIPE_STATUS_ID_AVAILABLE;
5820 } else if (avail < 6000) {
5821 //status = SIPE_STATUS_ID_IDLE;
5822 status = SIPE_STATUS_ID_AVAILABLE;
5823 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5824 } else if (avail < 7500) {
5825 status = SIPE_STATUS_ID_BUSY;
5826 } else if (avail < 9000) {
5827 //status = SIPE_STATUS_ID_BUSYIDLE;
5828 status = SIPE_STATUS_ID_BUSY;
5829 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5830 } else if (avail < 12000) {
5831 status = SIPE_STATUS_ID_DND;
5832 } else if (avail < 15000) {
5833 status = SIPE_STATUS_ID_BRB;
5834 } else if (avail < 18000) {
5835 status = SIPE_STATUS_ID_AWAY;
5836 } else {
5837 status = SIPE_STATUS_ID_OFFLINE;
5840 if (activity_desc && act) {
5841 g_free(*activity_desc);
5842 *activity_desc = g_strdup(act);
5845 return status;
5849 * Returns 2007-style availability value
5851 * @param sipe_status_id (in)
5852 * @param activity_token (out) Must be g_free()'d after use if consumed.
5854 static int
5855 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5857 int availability;
5858 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5860 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5861 availability = 15500;
5862 if (!activity_token || !(*activity_token)) {
5863 activity = SIPE_ACTIVITY_AWAY;
5865 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5866 availability = 12500;
5867 activity = SIPE_ACTIVITY_BRB;
5868 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5869 availability = 9500;
5870 activity = SIPE_ACTIVITY_DND;
5871 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5872 availability = 6500;
5873 if (!activity_token || !(*activity_token)) {
5874 activity = SIPE_ACTIVITY_BUSY;
5876 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5877 availability = 3500;
5878 activity = SIPE_ACTIVITY_ONLINE;
5879 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5880 availability = 0;
5881 } else {
5882 // Offline or invisible
5883 availability = 18500;
5884 activity = SIPE_ACTIVITY_OFFLINE;
5887 if (activity_token) {
5888 *activity_token = g_strdup(sipe_activity_map[activity].token);
5890 return availability;
5893 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5895 const char *uri;
5896 sipe_xml *xn_categories;
5897 const sipe_xml *xn_category;
5898 const char *status = NULL;
5899 gboolean do_update_status = FALSE;
5900 gboolean has_note_cleaned = FALSE;
5901 gboolean has_free_busy_cleaned = FALSE;
5903 xn_categories = sipe_xml_parse(data, len);
5904 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
5906 for (xn_category = sipe_xml_child(xn_categories, "category");
5907 xn_category ;
5908 xn_category = sipe_xml_twin(xn_category) )
5910 const sipe_xml *xn_node;
5911 const char *tmp;
5912 const char *attrVar = sipe_xml_attribute(xn_category, "name");
5913 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
5914 sipe_utils_str_to_time(tmp) : 0;
5916 /* contactCard */
5917 if (sipe_strequal(attrVar, "contactCard"))
5919 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
5921 if (card) {
5922 const sipe_xml *node;
5923 /* identity - Display Name and email */
5924 node = sipe_xml_child(card, "identity");
5925 if (node) {
5926 char* display_name = sipe_xml_data(
5927 sipe_xml_child(node, "name/displayName"));
5928 char* email = sipe_xml_data(
5929 sipe_xml_child(node, "email"));
5931 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5932 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5934 g_free(display_name);
5935 g_free(email);
5937 /* company */
5938 node = sipe_xml_child(card, "company");
5939 if (node) {
5940 char* company = sipe_xml_data(node);
5941 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5942 g_free(company);
5944 /* department */
5945 node = sipe_xml_child(card, "department");
5946 if (node) {
5947 char* department = sipe_xml_data(node);
5948 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5949 g_free(department);
5951 /* title */
5952 node = sipe_xml_child(card, "title");
5953 if (node) {
5954 char* title = sipe_xml_data(node);
5955 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5956 g_free(title);
5958 /* office */
5959 node = sipe_xml_child(card, "office");
5960 if (node) {
5961 char* office = sipe_xml_data(node);
5962 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5963 g_free(office);
5965 /* site (url) */
5966 node = sipe_xml_child(card, "url");
5967 if (node) {
5968 char* site = sipe_xml_data(node);
5969 sipe_update_user_info(sip, uri, SITE_PROP, site);
5970 g_free(site);
5972 /* phone */
5973 for (node = sipe_xml_child(card, "phone");
5974 node;
5975 node = sipe_xml_twin(node))
5977 const char *phone_type = sipe_xml_attribute(node, "type");
5978 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
5979 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
5981 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5983 g_free(phone);
5984 g_free(phone_display_string);
5986 /* address */
5987 for (node = sipe_xml_child(card, "address");
5988 node;
5989 node = sipe_xml_twin(node))
5991 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
5992 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
5993 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
5994 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
5995 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
5996 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
5998 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5999 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6000 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6001 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6002 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6004 g_free(street);
6005 g_free(city);
6006 g_free(state);
6007 g_free(zipcode);
6008 g_free(country_code);
6010 break;
6015 /* note */
6016 else if (sipe_strequal(attrVar, "note"))
6018 if (uri) {
6019 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6021 if (!has_note_cleaned) {
6022 has_note_cleaned = TRUE;
6024 g_free(sbuddy->note);
6025 sbuddy->note = NULL;
6026 sbuddy->is_oof_note = FALSE;
6027 sbuddy->note_since = publish_time;
6029 do_update_status = TRUE;
6031 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6032 /* clean up in case no 'note' element is supplied
6033 * which indicate note removal in client
6035 g_free(sbuddy->note);
6036 sbuddy->note = NULL;
6037 sbuddy->is_oof_note = FALSE;
6038 sbuddy->note_since = publish_time;
6040 xn_node = sipe_xml_child(xn_category, "note/body");
6041 if (xn_node) {
6042 char *tmp;
6043 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6044 g_free(tmp);
6045 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6046 sbuddy->note_since = publish_time;
6048 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6049 uri, sbuddy->note ? sbuddy->note : "");
6051 /* to trigger UI refresh in case no status info is supplied in this update */
6052 do_update_status = TRUE;
6056 /* state */
6057 else if(sipe_strequal(attrVar, "state"))
6059 char *tmp;
6060 int availability;
6061 const sipe_xml *xn_availability;
6062 const sipe_xml *xn_activity;
6063 const sipe_xml *xn_meeting_subject;
6064 const sipe_xml *xn_meeting_location;
6065 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6067 xn_node = sipe_xml_child(xn_category, "state");
6068 if (!xn_node) continue;
6069 xn_availability = sipe_xml_child(xn_node, "availability");
6070 if (!xn_availability) continue;
6071 xn_activity = sipe_xml_child(xn_node, "activity");
6072 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6073 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6075 tmp = sipe_xml_data(xn_availability);
6076 availability = atoi(tmp);
6077 g_free(tmp);
6079 /* activity, meeting_subject, meeting_location */
6080 if (sbuddy) {
6081 char *tmp = NULL;
6083 /* activity */
6084 g_free(sbuddy->activity);
6085 sbuddy->activity = NULL;
6086 if (xn_activity) {
6087 const char *token = sipe_xml_attribute(xn_activity, "token");
6088 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6090 /* from token */
6091 if (!is_empty(token)) {
6092 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6094 /* from custom element */
6095 if (xn_custom) {
6096 char *custom = sipe_xml_data(xn_custom);
6098 if (!is_empty(custom)) {
6099 sbuddy->activity = custom;
6100 custom = NULL;
6102 g_free(custom);
6105 /* meeting_subject */
6106 g_free(sbuddy->meeting_subject);
6107 sbuddy->meeting_subject = NULL;
6108 if (xn_meeting_subject) {
6109 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6111 if (!is_empty(meeting_subject)) {
6112 sbuddy->meeting_subject = meeting_subject;
6113 meeting_subject = NULL;
6115 g_free(meeting_subject);
6117 /* meeting_location */
6118 g_free(sbuddy->meeting_location);
6119 sbuddy->meeting_location = NULL;
6120 if (xn_meeting_location) {
6121 char *meeting_location = sipe_xml_data(xn_meeting_location);
6123 if (!is_empty(meeting_location)) {
6124 sbuddy->meeting_location = meeting_location;
6125 meeting_location = NULL;
6127 g_free(meeting_location);
6130 status = sipe_get_status_by_availability(availability, &tmp);
6131 if (sbuddy->activity && tmp) {
6132 char *tmp2 = sbuddy->activity;
6134 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6135 g_free(tmp);
6136 g_free(tmp2);
6137 } else if (tmp) {
6138 sbuddy->activity = tmp;
6142 do_update_status = TRUE;
6144 /* calendarData */
6145 else if(sipe_strequal(attrVar, "calendarData"))
6147 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6148 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6149 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6151 if (sbuddy && xn_free_busy) {
6152 if (!has_free_busy_cleaned) {
6153 has_free_busy_cleaned = TRUE;
6155 g_free(sbuddy->cal_start_time);
6156 sbuddy->cal_start_time = NULL;
6158 g_free(sbuddy->cal_free_busy_base64);
6159 sbuddy->cal_free_busy_base64 = NULL;
6161 g_free(sbuddy->cal_free_busy);
6162 sbuddy->cal_free_busy = NULL;
6164 sbuddy->cal_free_busy_published = publish_time;
6167 if (publish_time >= sbuddy->cal_free_busy_published) {
6168 g_free(sbuddy->cal_start_time);
6169 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6171 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6172 15 : 0;
6174 g_free(sbuddy->cal_free_busy_base64);
6175 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6177 g_free(sbuddy->cal_free_busy);
6178 sbuddy->cal_free_busy = NULL;
6180 sbuddy->cal_free_busy_published = publish_time;
6182 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s", sbuddy->cal_start_time, sbuddy->cal_granularity, sbuddy->cal_free_busy_base64);
6186 if (sbuddy && xn_working_hours) {
6187 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6192 if (do_update_status) {
6193 if (!status) { /* no status category in this update, using contact's current status */
6194 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6195 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6196 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6197 status = purple_status_get_id(pstatus);
6200 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6201 sipe_got_user_status(sip, uri, status);
6204 sipe_xml_free(xn_categories);
6207 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6209 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6210 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6211 payload->host = g_strdup(host);
6212 payload->buddies = server;
6213 sipe_subscribe_presence_batched_routed(sip, payload);
6214 sipe_subscribe_presence_batched_routed_free(payload);
6217 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6219 xmlnode *xn_list;
6220 xmlnode *xn_resource;
6221 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6222 g_free, NULL);
6223 GSList *server;
6224 gchar *host;
6226 xn_list = xmlnode_from_str(data, len);
6228 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6229 xn_resource;
6230 xn_resource = xmlnode_get_next_twin(xn_resource) )
6232 const char *uri, *state;
6233 xmlnode *xn_instance;
6235 xn_instance = xmlnode_get_child(xn_resource, "instance");
6236 if (!xn_instance) continue;
6238 uri = xmlnode_get_attrib(xn_resource, "uri");
6239 state = xmlnode_get_attrib(xn_instance, "state");
6240 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6242 if (strstr(state, "resubscribe")) {
6243 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6245 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6246 gchar *user = g_strdup(uri);
6247 host = g_strdup(poolFqdn);
6248 server = g_hash_table_lookup(servers, host);
6249 server = g_slist_append(server, user);
6250 g_hash_table_insert(servers, host, server);
6251 } else {
6252 sipe_subscribe_presence_single(sip, (void *) uri);
6257 /* Send out any deferred poolFqdn subscriptions */
6258 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6259 g_hash_table_destroy(servers);
6261 xmlnode_free(xn_list);
6264 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6266 gchar *uri;
6267 gchar *getbasic;
6268 gchar *activity = NULL;
6269 xmlnode *pidf;
6270 xmlnode *basicstatus = NULL, *tuple, *status;
6271 gboolean isonline = FALSE;
6272 xmlnode *display_name_node;
6274 pidf = xmlnode_from_str(data, len);
6275 if (!pidf) {
6276 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6277 return;
6280 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6282 if ((status = xmlnode_get_child(tuple, "status"))) {
6283 basicstatus = xmlnode_get_child(status, "basic");
6287 if (!basicstatus) {
6288 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6289 xmlnode_free(pidf);
6290 return;
6293 getbasic = xmlnode_get_data(basicstatus);
6294 if (!getbasic) {
6295 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6296 xmlnode_free(pidf);
6297 return;
6300 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6301 if (strstr(getbasic, "open")) {
6302 isonline = TRUE;
6304 g_free(getbasic);
6306 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6308 display_name_node = xmlnode_get_child(pidf, "display-name");
6309 if (display_name_node) {
6310 char * display_name = xmlnode_get_data(display_name_node);
6312 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6313 g_free(display_name);
6316 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6317 if ((status = xmlnode_get_child(tuple, "status"))) {
6318 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6319 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6320 activity = xmlnode_get_data(basicstatus);
6321 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6327 if (isonline) {
6328 const gchar * status_id = NULL;
6329 if (activity) {
6330 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6331 status_id = SIPE_STATUS_ID_BUSY;
6332 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6333 status_id = SIPE_STATUS_ID_AWAY;
6337 if (!status_id) {
6338 status_id = SIPE_STATUS_ID_AVAILABLE;
6341 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6342 sipe_got_user_status(sip, uri, status_id);
6343 } else {
6344 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6347 g_free(activity);
6348 g_free(uri);
6349 xmlnode_free(pidf);
6352 /** 2005 */
6353 static void
6354 sipe_user_info_has_updated(struct sipe_account_data *sip,
6355 xmlnode *xn_userinfo)
6357 xmlnode *xn_states;
6359 g_free(sip->user_states);
6360 sip->user_states = NULL;
6361 if ((xn_states = xmlnode_get_child(xn_userinfo, "states")) != NULL) {
6362 gchar *orig = sip->user_states = xmlnode_to_str(xn_states, NULL);
6364 /* this is a hack-around to remove added newline after inner element,
6365 * state in this case, where it shouldn't be.
6366 * After several use of xmlnode_to_str, amount of added newlines
6367 * grows significantly.
6369 if (orig) {
6370 gchar c, *stripped = orig;
6371 while ((c = *orig++)) {
6372 if ((c != '\n') /* && (c != '\r') */) {
6373 *stripped++ = c;
6376 *stripped = '\0';
6380 /* Publish initial state if not yet.
6381 * Assuming this happens on initial responce to self subscription
6382 * so we've already updated our UserInfo.
6384 if (!sip->initial_state_published) {
6385 send_presence_soap(sip, FALSE);
6386 /* dalayed run */
6387 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6391 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6393 char *activity = NULL;
6394 const char *epid;
6395 const char *status_id = NULL;
6396 const char *name;
6397 char *uri;
6398 char *self_uri = sip_uri_self(sip);
6399 int avl;
6400 int act;
6401 const char *device_name = NULL;
6402 const char *cal_start_time = NULL;
6403 const char *cal_granularity = NULL;
6404 char *cal_free_busy_base64 = NULL;
6405 struct sipe_buddy *sbuddy;
6406 xmlnode *node;
6407 xmlnode *xn_presentity;
6408 xmlnode *xn_availability;
6409 xmlnode *xn_activity;
6410 xmlnode *xn_display_name;
6411 xmlnode *xn_email;
6412 xmlnode *xn_phone_number;
6413 xmlnode *xn_userinfo;
6414 xmlnode *xn_note;
6415 xmlnode *xn_oof;
6416 xmlnode *xn_state;
6417 xmlnode *xn_contact;
6418 char *note;
6419 char *free_activity;
6420 int user_avail;
6421 const char *user_avail_nil;
6422 int res_avail;
6423 time_t user_avail_since = 0;
6424 time_t activity_since = 0;
6426 /* fix for Reuters environment on Linux */
6427 if (data && strstr(data, "encoding=\"utf-16\"")) {
6428 char *tmp_data;
6429 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6430 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6431 g_free(tmp_data);
6432 } else {
6433 xn_presentity = xmlnode_from_str(data, len);
6436 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6437 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6438 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6439 xn_email = xmlnode_get_child(xn_presentity, "email");
6440 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6441 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6442 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6443 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6444 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6445 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6446 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6447 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6448 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6449 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6451 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6452 user_avail = 0;
6453 user_avail_since = 0;
6456 free_activity = NULL;
6458 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6459 uri = sip_uri_from_name(name);
6460 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6461 epid = xmlnode_get_attrib(xn_availability, "epid");
6462 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6464 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6465 res_avail = sipe_get_availability_by_status(status_id, NULL);
6466 if (user_avail > res_avail) {
6467 res_avail = user_avail;
6468 status_id = sipe_get_status_by_availability(user_avail, NULL);
6471 if (xn_display_name) {
6472 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6473 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6474 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6475 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6476 char *tel_uri = sip_to_tel_uri(phone_number);
6478 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6479 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6480 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6481 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6483 g_free(tel_uri);
6484 g_free(phone_label);
6485 g_free(phone_number);
6486 g_free(email);
6487 g_free(display_name);
6490 if (xn_contact) {
6491 /* tel */
6492 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6494 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6495 const char *phone_type = xmlnode_get_attrib(node, "type");
6496 char* phone = xmlnode_get_data(node);
6498 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6500 g_free(phone);
6504 /* devicePresence */
6505 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6506 xmlnode *xn_device_name;
6507 xmlnode *xn_calendar_info;
6508 xmlnode *xn_state;
6509 char *state;
6511 /* deviceName */
6512 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6513 xn_device_name = xmlnode_get_child(node, "deviceName");
6514 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6517 /* calendarInfo */
6518 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6519 if (xn_calendar_info) {
6520 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6522 if (cal_start_time) {
6523 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6524 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6526 if (cal_start_time_t_tmp > cal_start_time_t) {
6527 cal_start_time = cal_start_time_tmp;
6528 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6529 g_free(cal_free_busy_base64);
6530 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6532 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
6534 } else {
6535 cal_start_time = cal_start_time_tmp;
6536 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6537 g_free(cal_free_busy_base64);
6538 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6540 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
6544 /* state */
6545 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6546 if (xn_state) {
6547 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6548 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6550 state = xmlnode_get_data(xn_state);
6551 if (dev_avail_since > user_avail_since &&
6552 dev_avail >= res_avail)
6554 res_avail = dev_avail;
6555 if (!is_empty(state))
6557 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6558 g_free(activity);
6559 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6560 } else if (sipe_strequal(state, "presenting")) {
6561 g_free(activity);
6562 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6563 } else {
6564 activity = state;
6565 state = NULL;
6567 activity_since = dev_avail_since;
6569 status_id = sipe_get_status_by_availability(res_avail, &activity);
6571 g_free(state);
6575 /* oof */
6576 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6577 g_free(activity);
6578 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6579 activity_since = 0;
6582 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6583 if (sbuddy)
6585 g_free(sbuddy->activity);
6586 sbuddy->activity = activity;
6587 activity = NULL;
6589 sbuddy->activity_since = activity_since;
6591 sbuddy->user_avail = user_avail;
6592 sbuddy->user_avail_since = user_avail_since;
6594 g_free(sbuddy->note);
6595 sbuddy->note = NULL;
6596 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6598 sbuddy->is_oof_note = (xn_oof != NULL);
6600 g_free(sbuddy->device_name);
6601 sbuddy->device_name = NULL;
6602 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6604 if (!is_empty(cal_free_busy_base64)) {
6605 g_free(sbuddy->cal_start_time);
6606 sbuddy->cal_start_time = g_strdup(cal_start_time);
6608 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6610 g_free(sbuddy->cal_free_busy_base64);
6611 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6612 cal_free_busy_base64 = NULL;
6614 g_free(sbuddy->cal_free_busy);
6615 sbuddy->cal_free_busy = NULL;
6618 sbuddy->last_non_cal_status_id = status_id;
6619 g_free(sbuddy->last_non_cal_activity);
6620 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6622 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6623 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6625 sip->is_oof_note = sbuddy->is_oof_note;
6627 g_free(sip->note);
6628 sip->note = g_strdup(sbuddy->note);
6630 sip->note_since = time(NULL);
6633 g_free(sip->status);
6634 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6637 g_free(cal_free_busy_base64);
6638 g_free(activity);
6640 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6641 sipe_got_user_status(sip, uri, status_id);
6643 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6644 sipe_user_info_has_updated(sip, xn_userinfo);
6647 g_free(note);
6648 xmlnode_free(xn_presentity);
6649 g_free(uri);
6650 g_free(self_uri);
6653 static void sipe_presence_mime_cb(gpointer user_data,
6654 const gchar *type,
6655 const gchar *body,
6656 gsize length)
6658 if (strstr(type,"application/rlmi+xml")) {
6659 process_incoming_notify_rlmi_resub(user_data, body, length);
6660 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6661 process_incoming_notify_msrtc(user_data, body, length);
6662 } else {
6663 process_incoming_notify_rlmi(user_data, body, length);
6667 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6669 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6671 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6673 if (ctype &&
6674 (strstr(ctype, "application/rlmi+xml") ||
6675 strstr(ctype, "application/msrtc-event-categories+xml")))
6677 if (strstr(ctype, "multipart"))
6679 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
6681 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6683 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6685 else if(strstr(ctype, "application/rlmi+xml"))
6687 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6690 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6692 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6694 else
6696 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6700 static void sipe_presence_timeout_mime_cb(gpointer user_data,
6701 SIPE_UNUSED_PARAMETER const gchar *type,
6702 const gchar *body,
6703 gsize length)
6705 GSList **buddies = user_data;
6706 xmlnode *xml = xmlnode_from_str(body, length);
6708 if (xml && !sipe_strequal(xml->name, "list")) {
6709 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6710 *buddies = g_slist_append(*buddies, uri);
6713 xmlnode_free(xml);
6716 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6718 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6719 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6721 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
6723 if (ctype &&
6724 strstr(ctype, "multipart") &&
6725 (strstr(ctype, "application/rlmi+xml") ||
6726 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6727 GSList *buddies = NULL;
6728 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6730 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
6732 payload->host = g_strdup(who);
6733 payload->buddies = buddies;
6734 sipe_schedule_action(action_name, timeout,
6735 sipe_subscribe_presence_batched_routed,
6736 sipe_subscribe_presence_batched_routed_free,
6737 sip, payload);
6738 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
6740 } else {
6741 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6742 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
6744 g_free(action_name);
6748 * Dispatcher for all incoming subscription information
6749 * whether it comes from NOTIFY, BENOTIFY requests or
6750 * piggy-backed to subscription's OK responce.
6752 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6753 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6755 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6757 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6758 const gchar *event = sipmsg_find_header(msg, "Event");
6759 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6760 char *tmp;
6762 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
6763 event ? event : "",
6764 tmp = fix_newlines(msg->body));
6765 g_free(tmp);
6766 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
6768 /* implicit subscriptions */
6769 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6770 sipe_process_imdn(sip, msg);
6773 if (event) {
6774 /* for one off subscriptions (send with Expire: 0) */
6775 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
6777 sipe_process_provisioning_v2(sip, msg);
6779 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
6781 sipe_process_provisioning(sip, msg);
6783 else if (sipe_strcase_equal(event, "presence"))
6785 sipe_process_presence(sip, msg);
6787 else if (sipe_strcase_equal(event, "registration-notify"))
6789 sipe_process_registration_notify(sip, msg);
6792 if (!subscription_state || strstr(subscription_state, "active"))
6794 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
6796 sipe_process_roaming_contacts(sip, msg);
6798 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
6800 sipe_process_roaming_self(sip, msg);
6802 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
6804 sipe_process_roaming_acl(sip, msg);
6806 else if (sipe_strcase_equal(event, "presence.wpending"))
6808 sipe_process_presence_wpending(sip, msg);
6810 else if (sipe_strcase_equal(event, "conference"))
6812 sipe_process_conference(sip, msg);
6817 /* The server sends status 'terminated' */
6818 if (subscription_state && strstr(subscription_state, "terminated") ) {
6819 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6820 gchar *key = sipe_get_subscription_key(event, who);
6822 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
6823 g_free(who);
6825 if (g_hash_table_lookup(sip->subscriptions, key)) {
6826 g_hash_table_remove(sip->subscriptions, key);
6827 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
6830 g_free(key);
6833 if (!request && event) {
6834 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6835 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6836 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
6838 if (timeout) {
6839 /* 2 min ahead of expiration */
6840 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6842 if (sipe_strcase_equal(event, "presence.wpending") &&
6843 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6845 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6846 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
6847 g_free(action_name);
6849 else if (sipe_strcase_equal(event, "presence") &&
6850 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6852 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6853 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6855 if (sip->batched_support) {
6856 sipe_process_presence_timeout(sip, msg, who, timeout);
6858 else {
6859 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6860 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
6862 g_free(action_name);
6863 g_free(who);
6868 /* The client responses on received a NOTIFY message */
6869 if (request && !benotify)
6871 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6876 * Whether user manually changed status or
6877 * it was changed automatically due to user
6878 * became inactive/active again
6880 static gboolean
6881 sipe_is_user_state(struct sipe_account_data *sip)
6883 gboolean res;
6884 time_t now = time(NULL);
6886 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6887 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
6889 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6891 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
6892 return res;
6895 static void
6896 send_presence_soap0(struct sipe_account_data *sip,
6897 gboolean do_publish_calendar,
6898 gboolean do_reset_status)
6900 struct sipe_ews* ews = sip->ews;
6901 int availability = 0;
6902 int activity = 0;
6903 gchar *body;
6904 gchar *tmp;
6905 gchar *tmp2 = NULL;
6906 gchar *res_note = NULL;
6907 gchar *res_oof = NULL;
6908 const gchar *note_pub = NULL;
6909 gchar *states = NULL;
6910 gchar *calendar_data = NULL;
6911 gchar *epid = get_epid(sip);
6912 time_t now = time(NULL);
6913 gchar *since_time_str = sipe_utils_time_to_str(now);
6914 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
6915 const char *user_input;
6916 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
6918 if (oof_note && sip->note) {
6919 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6920 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6923 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
6925 if (!sip->initial_state_published ||
6926 do_reset_status)
6928 g_free(sip->status);
6929 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6932 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6934 /* Note */
6935 if (pub_oof) {
6936 note_pub = oof_note;
6937 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6938 ews->published = TRUE;
6939 } else if (sip->note) {
6940 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
6941 g_free(sip->note);
6942 sip->note = NULL;
6943 sip->is_oof_note = FALSE;
6944 sip->note_since = 0;
6945 } else {
6946 note_pub = sip->note;
6947 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6951 if (note_pub)
6953 /* to protocol internal plain text format */
6954 tmp = sipe_backend_markup_strip_html(note_pub);
6955 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6956 g_free(tmp);
6959 /* User State */
6960 if (!do_reset_status) {
6961 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
6963 gchar *activity_token = NULL;
6964 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6966 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6967 avail_2007,
6968 since_time_str,
6969 epid,
6970 activity_token);
6971 g_free(activity_token);
6973 else /* preserve existing publication */
6975 if (sip->user_states) {
6976 states = g_strdup(sip->user_states);
6979 } else {
6980 /* do nothing - then User state will be erased */
6982 sip->initial_state_published = TRUE;
6984 /* CalendarInfo */
6985 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
6987 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
6988 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
6989 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6990 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
6991 fb_start_str,
6992 free_busy_base64);
6993 g_free(fb_start_str);
6994 g_free(free_busy_base64);
6997 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6999 /* forming resulting XML */
7000 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7001 sip->username,
7002 availability,
7003 activity,
7004 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7005 res_note ? res_note : "",
7006 res_oof ? res_oof : "",
7007 states ? states : "",
7008 calendar_data ? calendar_data : "",
7009 epid,
7010 since_time_str,
7011 since_time_str,
7012 user_input);
7013 g_free(tmp);
7014 g_free(tmp2);
7015 g_free(res_note);
7016 g_free(states);
7017 g_free(calendar_data);
7019 send_soap_request(sip, body);
7021 g_free(body);
7022 g_free(since_time_str);
7023 g_free(epid);
7026 void
7027 send_presence_soap(struct sipe_account_data *sip,
7028 gboolean do_publish_calendar)
7030 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7034 static gboolean
7035 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7036 struct sipmsg *msg,
7037 struct transaction *trans)
7039 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7041 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7042 xmlnode *xml;
7043 xmlnode *node;
7044 gchar *fault_code;
7045 GHashTable *faults;
7046 int index_our;
7047 gboolean has_device_publication = FALSE;
7049 xml = xmlnode_from_str(msg->body, msg->bodylen);
7051 /* test if version mismatch fault */
7052 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
7053 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7054 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7055 g_free(fault_code);
7056 xmlnode_free(xml);
7057 return TRUE;
7059 g_free(fault_code);
7061 /* accumulating information about faulty versions */
7062 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7063 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
7064 node;
7065 node = xmlnode_get_next_twin(node))
7067 const gchar *index = xmlnode_get_attrib(node, "index");
7068 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
7070 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7071 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7073 xmlnode_free(xml);
7075 /* here we are parsing own request to figure out what publication
7076 * referensed here only by index went wrong
7078 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
7080 /* publication */
7081 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
7082 index_our = 1; /* starts with 1 - our first publication */
7083 node;
7084 node = xmlnode_get_next_twin(node), index_our++)
7086 gchar *idx = g_strdup_printf("%d", index_our);
7087 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7088 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
7089 g_free(idx);
7091 if (sipe_strequal("device", categoryName)) {
7092 has_device_publication = TRUE;
7095 if (curVersion) { /* fault exist on this index */
7096 const gchar *container = xmlnode_get_attrib(node, "container");
7097 const gchar *instance = xmlnode_get_attrib(node, "instance");
7098 /* key is <category><instance><container> */
7099 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7100 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7102 if (category) {
7103 struct sipe_publication *publication =
7104 g_hash_table_lookup(category, key);
7106 SIPE_DEBUG_INFO("key is %s", key);
7108 if (publication) {
7109 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7110 key, curVersion, publication->version);
7111 /* updating publication's version to the correct one */
7112 publication->version = atoi(curVersion);
7114 } else {
7115 /* We somehow lost this category from our publications... */
7116 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7117 publication->category = g_strdup(categoryName);
7118 publication->instance = atoi(instance);
7119 publication->container = atoi(container);
7120 publication->version = atoi(curVersion);
7121 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7122 g_free, (GDestroyNotify)free_publication);
7123 g_hash_table_insert(category, g_strdup(key), publication);
7124 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7125 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7127 g_free(key);
7130 xmlnode_free(xml);
7131 g_hash_table_destroy(faults);
7133 /* rebublishing with right versions */
7134 if (has_device_publication) {
7135 send_publish_category_initial(sip);
7136 } else {
7137 send_presence_status(sip);
7140 return TRUE;
7144 * Returns 'device' XML part for publication.
7145 * Must be g_free'd after use.
7147 static gchar *
7148 sipe_publish_get_category_device(struct sipe_account_data *sip)
7150 gchar *uri;
7151 gchar *doc;
7152 gchar *epid = get_epid(sip);
7153 gchar *uuid = generateUUIDfromEPID(epid);
7154 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7155 /* key is <category><instance><container> */
7156 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7157 struct sipe_publication *publication =
7158 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7160 g_free(key);
7161 g_free(epid);
7163 uri = sip_uri_self(sip);
7164 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7165 device_instance,
7166 publication ? publication->version : 0,
7167 uuid,
7168 uri,
7169 "00:00:00+01:00", /* @TODO make timezone real*/
7170 g_get_host_name()
7173 g_free(uri);
7174 g_free(uuid);
7176 return doc;
7180 * A service method - use
7181 * - send_publish_get_category_state_machine and
7182 * - send_publish_get_category_state_user instead.
7183 * Must be g_free'd after use.
7185 static gchar *
7186 sipe_publish_get_category_state(struct sipe_account_data *sip,
7187 gboolean is_user_state)
7189 int availability = sipe_get_availability_by_status(sip->status, NULL);
7190 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7191 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7192 /* key is <category><instance><container> */
7193 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7194 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7195 struct sipe_publication *publication_2 =
7196 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7197 struct sipe_publication *publication_3 =
7198 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7200 g_free(key_2);
7201 g_free(key_3);
7203 if (publication_2 && (publication_2->availability == availability))
7205 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7206 return NULL; /* nothing to update */
7209 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7210 instance,
7211 publication_2 ? publication_2->version : 0,
7212 availability,
7213 instance,
7214 publication_3 ? publication_3->version : 0,
7215 availability);
7219 * Only Busy and OOF calendar event are published.
7220 * Different instances are used for that.
7222 * Must be g_free'd after use.
7224 static gchar *
7225 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7226 struct sipe_cal_event *event,
7227 const char *uri,
7228 int cal_satus)
7230 gchar *start_time_str;
7231 int availability = 0;
7232 gchar *res;
7233 gchar *tmp = NULL;
7234 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7235 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7236 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7238 /* key is <category><instance><container> */
7239 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7240 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7241 struct sipe_publication *publication_2 =
7242 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7243 struct sipe_publication *publication_3 =
7244 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7246 g_free(key_2);
7247 g_free(key_3);
7249 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7250 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7251 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7252 return NULL;
7255 if (event &&
7256 publication_3 &&
7257 (publication_3->availability == availability) &&
7258 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7260 g_free(tmp);
7261 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7262 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7263 return NULL; /* nothing to update */
7265 g_free(tmp);
7267 if (event &&
7268 (event->cal_status == SIPE_CAL_BUSY ||
7269 event->cal_status == SIPE_CAL_OOF))
7271 gchar *availability_xml_str = NULL;
7272 gchar *activity_xml_str = NULL;
7274 if (event->cal_status == SIPE_CAL_BUSY) {
7275 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7278 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7279 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7280 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7281 "minAvailability=\"6500\"",
7282 "maxAvailability=\"8999\"");
7283 } else if (event->cal_status == SIPE_CAL_OOF) {
7284 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7285 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7286 "minAvailability=\"12000\"",
7287 "");
7289 start_time_str = sipe_utils_time_to_str(event->start_time);
7291 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7292 instance,
7293 publication_2 ? publication_2->version : 0,
7294 uri,
7295 start_time_str,
7296 availability_xml_str ? availability_xml_str : "",
7297 activity_xml_str ? activity_xml_str : "",
7298 event->subject ? event->subject : "",
7299 event->location ? event->location : "",
7301 instance,
7302 publication_3 ? publication_3->version : 0,
7303 uri,
7304 start_time_str,
7305 availability_xml_str ? availability_xml_str : "",
7306 activity_xml_str ? activity_xml_str : "",
7307 event->subject ? event->subject : "",
7308 event->location ? event->location : ""
7310 g_free(start_time_str);
7311 g_free(availability_xml_str);
7312 g_free(activity_xml_str);
7315 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7317 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7318 instance,
7319 publication_2 ? publication_2->version : 0,
7321 instance,
7322 publication_3 ? publication_3->version : 0
7326 return res;
7330 * Returns 'machineState' XML part for publication.
7331 * Must be g_free'd after use.
7333 static gchar *
7334 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7336 return sipe_publish_get_category_state(sip, FALSE);
7340 * Returns 'userState' XML part for publication.
7341 * Must be g_free'd after use.
7343 static gchar *
7344 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7346 return sipe_publish_get_category_state(sip, TRUE);
7350 * Returns 'note' XML part for publication.
7351 * Must be g_free'd after use.
7353 * Protocol format for Note is plain text.
7355 * @param note a note in Sipe internal HTML format
7356 * @param note_type either personal or OOF
7358 static gchar *
7359 sipe_publish_get_category_note(struct sipe_account_data *sip,
7360 const char *note, /* html */
7361 const char *note_type,
7362 time_t note_start,
7363 time_t note_end)
7365 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7366 /* key is <category><instance><container> */
7367 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7368 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7369 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7371 struct sipe_publication *publication_note_200 =
7372 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7373 struct sipe_publication *publication_note_300 =
7374 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7375 struct sipe_publication *publication_note_400 =
7376 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7378 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7379 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7380 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7381 char *res, *tmp1, *tmp2, *tmp3;
7382 char *start_time_attr;
7383 char *end_time_attr;
7385 g_free(tmp);
7386 tmp = NULL;
7387 g_free(key_note_200);
7388 g_free(key_note_300);
7389 g_free(key_note_400);
7391 /* we even need to republish empty note */
7392 if (sipe_strequal(n1, n2))
7394 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7395 g_free(n1);
7396 return NULL; /* nothing to update */
7399 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7400 g_free(tmp);
7401 tmp = NULL;
7402 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7403 g_free(tmp);
7405 if (n1) {
7406 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7407 instance,
7408 200,
7409 publication_note_200 ? publication_note_200->version : 0,
7410 note_type,
7411 start_time_attr ? start_time_attr : "",
7412 end_time_attr ? end_time_attr : "",
7413 n1);
7415 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7416 instance,
7417 300,
7418 publication_note_300 ? publication_note_300->version : 0,
7419 note_type,
7420 start_time_attr ? start_time_attr : "",
7421 end_time_attr ? end_time_attr : "",
7422 n1);
7424 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7425 instance,
7426 400,
7427 publication_note_400 ? publication_note_400->version : 0,
7428 note_type,
7429 start_time_attr ? start_time_attr : "",
7430 end_time_attr ? end_time_attr : "",
7431 n1);
7432 } else {
7433 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7434 "note",
7435 instance,
7436 200,
7437 publication_note_200 ? publication_note_200->version : 0,
7438 "static");
7439 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7440 "note",
7441 instance,
7442 300,
7443 publication_note_200 ? publication_note_200->version : 0,
7444 "static");
7445 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7446 "note",
7447 instance,
7448 400,
7449 publication_note_200 ? publication_note_200->version : 0,
7450 "static");
7452 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7454 g_free(start_time_attr);
7455 g_free(end_time_attr);
7456 g_free(tmp1);
7457 g_free(tmp2);
7458 g_free(tmp3);
7459 g_free(n1);
7461 return res;
7465 * Returns 'calendarData' XML part with WorkingHours for publication.
7466 * Must be g_free'd after use.
7468 static gchar *
7469 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7471 struct sipe_ews* ews = sip->ews;
7473 /* key is <category><instance><container> */
7474 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7475 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7476 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7477 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7478 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7479 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7481 struct sipe_publication *publication_cal_1 =
7482 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7483 struct sipe_publication *publication_cal_100 =
7484 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7485 struct sipe_publication *publication_cal_200 =
7486 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7487 struct sipe_publication *publication_cal_300 =
7488 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7489 struct sipe_publication *publication_cal_400 =
7490 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7491 struct sipe_publication *publication_cal_32000 =
7492 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7494 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7495 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7497 g_free(key_cal_1);
7498 g_free(key_cal_100);
7499 g_free(key_cal_200);
7500 g_free(key_cal_300);
7501 g_free(key_cal_400);
7502 g_free(key_cal_32000);
7504 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7505 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7506 return NULL;
7509 if (sipe_strequal(n1, n2))
7511 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7512 return NULL; /* nothing to update */
7515 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7516 /* 1 */
7517 publication_cal_1 ? publication_cal_1->version : 0,
7518 ews->email,
7519 ews->working_hours_xml_str,
7520 /* 100 - Public */
7521 publication_cal_100 ? publication_cal_100->version : 0,
7522 /* 200 - Company */
7523 publication_cal_200 ? publication_cal_200->version : 0,
7524 ews->email,
7525 ews->working_hours_xml_str,
7526 /* 300 - Team */
7527 publication_cal_300 ? publication_cal_300->version : 0,
7528 ews->email,
7529 ews->working_hours_xml_str,
7530 /* 400 - Personal */
7531 publication_cal_400 ? publication_cal_400->version : 0,
7532 ews->email,
7533 ews->working_hours_xml_str,
7534 /* 32000 - Blocked */
7535 publication_cal_32000 ? publication_cal_32000->version : 0
7540 * Returns 'calendarData' XML part with FreeBusy for publication.
7541 * Must be g_free'd after use.
7543 static gchar *
7544 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7546 struct sipe_ews* ews = sip->ews;
7547 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7548 char *fb_start_str;
7549 char *free_busy_base64;
7550 const char *st;
7551 const char *fb;
7552 char *res;
7554 /* key is <category><instance><container> */
7555 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7556 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7557 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7558 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7559 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7560 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7562 struct sipe_publication *publication_cal_1 =
7563 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7564 struct sipe_publication *publication_cal_100 =
7565 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7566 struct sipe_publication *publication_cal_200 =
7567 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7568 struct sipe_publication *publication_cal_300 =
7569 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7570 struct sipe_publication *publication_cal_400 =
7571 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7572 struct sipe_publication *publication_cal_32000 =
7573 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7575 g_free(key_cal_1);
7576 g_free(key_cal_100);
7577 g_free(key_cal_200);
7578 g_free(key_cal_300);
7579 g_free(key_cal_400);
7580 g_free(key_cal_32000);
7582 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7583 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7584 return NULL;
7587 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7588 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7590 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7591 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7593 /* we will rebuplish the same data to refresh publication time,
7594 * so if data from multiple sources, most recent will be choosen
7596 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7598 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7599 // g_free(fb_start_str);
7600 // g_free(free_busy_base64);
7601 // return NULL; /* nothing to update */
7604 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7605 /* 1 */
7606 cal_data_instance,
7607 publication_cal_1 ? publication_cal_1->version : 0,
7608 /* 100 - Public */
7609 cal_data_instance,
7610 publication_cal_100 ? publication_cal_100->version : 0,
7611 /* 200 - Company */
7612 cal_data_instance,
7613 publication_cal_200 ? publication_cal_200->version : 0,
7614 ews->email,
7615 fb_start_str,
7616 free_busy_base64,
7617 /* 300 - Team */
7618 cal_data_instance,
7619 publication_cal_300 ? publication_cal_300->version : 0,
7620 ews->email,
7621 fb_start_str,
7622 free_busy_base64,
7623 /* 400 - Personal */
7624 cal_data_instance,
7625 publication_cal_400 ? publication_cal_400->version : 0,
7626 ews->email,
7627 fb_start_str,
7628 free_busy_base64,
7629 /* 32000 - Blocked */
7630 cal_data_instance,
7631 publication_cal_32000 ? publication_cal_32000->version : 0
7634 g_free(fb_start_str);
7635 g_free(free_busy_base64);
7636 return res;
7639 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7641 gchar *uri;
7642 gchar *doc;
7643 gchar *tmp;
7644 gchar *hdr;
7646 uri = sip_uri_self(sip);
7647 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7648 uri,
7649 publications);
7651 tmp = get_contact(sip);
7652 hdr = g_strdup_printf("Contact: %s\r\n"
7653 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7655 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7657 g_free(tmp);
7658 g_free(hdr);
7659 g_free(uri);
7660 g_free(doc);
7663 static void
7664 send_publish_category_initial(struct sipe_account_data *sip)
7666 gchar *pub_device = sipe_publish_get_category_device(sip);
7667 gchar *pub_machine;
7668 gchar *publications;
7670 g_free(sip->status);
7671 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7673 pub_machine = sipe_publish_get_category_state_machine(sip);
7674 publications = g_strdup_printf("%s%s",
7675 pub_device,
7676 pub_machine ? pub_machine : "");
7677 g_free(pub_device);
7678 g_free(pub_machine);
7680 send_presence_publish(sip, publications);
7681 g_free(publications);
7684 static void
7685 send_presence_category_publish(struct sipe_account_data *sip)
7687 gchar *pub_state = sipe_is_user_state(sip) ?
7688 sipe_publish_get_category_state_user(sip) :
7689 sipe_publish_get_category_state_machine(sip);
7690 gchar *pub_note = sipe_publish_get_category_note(sip,
7691 sip->note,
7692 sip->is_oof_note ? "OOF" : "personal",
7695 gchar *publications;
7697 if (!pub_state && !pub_note) {
7698 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
7699 return;
7702 publications = g_strdup_printf("%s%s",
7703 pub_state ? pub_state : "",
7704 pub_note ? pub_note : "");
7706 g_free(pub_state);
7707 g_free(pub_note);
7709 send_presence_publish(sip, publications);
7710 g_free(publications);
7714 * Publishes self status
7715 * based on own calendar information.
7717 * For 2007+
7719 void
7720 publish_calendar_status_self(struct sipe_account_data *sip)
7722 struct sipe_cal_event* event = NULL;
7723 gchar *pub_cal_working_hours = NULL;
7724 gchar *pub_cal_free_busy = NULL;
7725 gchar *pub_calendar = NULL;
7726 gchar *pub_calendar2 = NULL;
7727 gchar *pub_oof_note = NULL;
7728 const gchar *oof_note;
7729 time_t oof_start = 0;
7730 time_t oof_end = 0;
7732 if (!sip->ews) {
7733 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
7734 return;
7737 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
7738 if (sip->ews->cal_events) {
7739 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7742 if (!event) {
7743 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
7744 } else {
7745 char *desc = sipe_cal_event_describe(event);
7746 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7747 g_free(desc);
7750 /* Logic
7751 if OOF
7752 OOF publish, Busy clean
7753 ilse if Busy
7754 OOF clean, Busy publish
7755 else
7756 OOF clean, Busy clean
7758 if (event && event->cal_status == SIPE_CAL_OOF) {
7759 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7760 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7761 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7762 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7763 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7764 } else {
7765 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7766 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7769 oof_note = sipe_ews_get_oof_note(sip->ews);
7770 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7771 oof_start = sip->ews->oof_start;
7772 oof_end = sip->ews->oof_end;
7774 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7776 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7777 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7779 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7780 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
7781 } else {
7782 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7783 pub_cal_working_hours ? pub_cal_working_hours : "",
7784 pub_cal_free_busy ? pub_cal_free_busy : "",
7785 pub_calendar ? pub_calendar : "",
7786 pub_calendar2 ? pub_calendar2 : "",
7787 pub_oof_note ? pub_oof_note : "");
7789 send_presence_publish(sip, publications);
7790 g_free(publications);
7793 g_free(pub_cal_working_hours);
7794 g_free(pub_cal_free_busy);
7795 g_free(pub_calendar);
7796 g_free(pub_calendar2);
7797 g_free(pub_oof_note);
7799 /* repeat scheduling */
7800 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7803 static void send_presence_status(struct sipe_account_data *sip)
7805 PurpleStatus * status = purple_account_get_active_status(sip->account);
7807 if (!status) return;
7809 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
7810 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7811 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7813 if (sip->ocs2007) {
7814 send_presence_category_publish(sip);
7815 } else {
7816 send_presence_soap(sip, FALSE);
7820 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7822 gboolean found = FALSE;
7823 const char *method = msg->method ? msg->method : "NOT FOUND";
7824 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
7825 if (msg->response == 0) { /* request */
7826 if (sipe_strequal(method, "MESSAGE")) {
7827 process_incoming_message(sip, msg);
7828 found = TRUE;
7829 } else if (sipe_strequal(method, "NOTIFY")) {
7830 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
7831 process_incoming_notify(sip, msg, TRUE, FALSE);
7832 found = TRUE;
7833 } else if (sipe_strequal(method, "BENOTIFY")) {
7834 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
7835 process_incoming_notify(sip, msg, TRUE, TRUE);
7836 found = TRUE;
7837 } else if (sipe_strequal(method, "INVITE")) {
7838 process_incoming_invite(sip, msg);
7839 found = TRUE;
7840 } else if (sipe_strequal(method, "REFER")) {
7841 process_incoming_refer(sip, msg);
7842 found = TRUE;
7843 } else if (sipe_strequal(method, "OPTIONS")) {
7844 process_incoming_options(sip, msg);
7845 found = TRUE;
7846 } else if (sipe_strequal(method, "INFO")) {
7847 process_incoming_info(sip, msg);
7848 found = TRUE;
7849 } else if (sipe_strequal(method, "ACK")) {
7850 // ACK's don't need any response
7851 found = TRUE;
7852 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7853 // LCS 2005 sends us these - just respond 200 OK
7854 found = TRUE;
7855 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7856 } else if (sipe_strequal(method, "BYE")) {
7857 process_incoming_bye(sip, msg);
7858 found = TRUE;
7859 } else {
7860 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7862 } else { /* response */
7863 struct transaction *trans = transactions_find(sip, msg);
7864 if (trans) {
7865 if (msg->response == 407) {
7866 gchar *resend, *auth;
7867 const gchar *ptmp;
7869 if (sip->proxy.retries > 30) return;
7870 sip->proxy.retries++;
7871 /* do proxy authentication */
7873 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7875 fill_auth(ptmp, &sip->proxy);
7876 auth = auth_header(sip, &sip->proxy, trans->msg);
7877 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7878 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7879 g_free(auth);
7880 resend = sipmsg_to_string(trans->msg);
7881 /* resend request */
7882 sendout_pkt(sip->gc, resend);
7883 g_free(resend);
7884 } else {
7885 if (msg->response < 200) {
7886 /* ignore provisional response */
7887 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
7888 } else {
7889 sip->proxy.retries = 0;
7890 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7891 if (msg->response == 401)
7893 sip->registrar.retries++;
7895 else
7897 sip->registrar.retries = 0;
7899 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
7900 } else {
7901 if (msg->response == 401) {
7902 gchar *resend, *auth, *ptmp;
7903 const char* auth_scheme;
7905 if (sip->registrar.retries > 4) return;
7906 sip->registrar.retries++;
7908 auth_scheme = sipe_get_auth_scheme_name(sip);
7909 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7911 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
7912 if (!ptmp) {
7913 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7914 sip->gc->wants_to_die = TRUE;
7915 purple_connection_error(sip->gc, tmp2);
7916 g_free(tmp2);
7917 return;
7920 fill_auth(ptmp, &sip->registrar);
7921 auth = auth_header(sip, &sip->registrar, trans->msg);
7922 sipmsg_remove_header_now(trans->msg, "Authorization");
7923 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7924 g_free(auth);
7925 resend = sipmsg_to_string(trans->msg);
7926 /* resend request */
7927 sendout_pkt(sip->gc, resend);
7928 g_free(resend);
7932 if (trans->callback) {
7933 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
7934 /* call the callback to process response*/
7935 (trans->callback)(sip, msg, trans);
7938 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
7939 transactions_remove(sip, trans);
7943 found = TRUE;
7944 } else {
7945 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
7948 if (!found) {
7949 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
7953 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7955 char *cur;
7956 char *dummy;
7957 char *tmp;
7958 struct sipmsg *msg;
7959 int restlen;
7960 cur = conn->inbuf;
7962 /* according to the RFC remove CRLF at the beginning */
7963 while (*cur == '\r' || *cur == '\n') {
7964 cur++;
7966 if (cur != conn->inbuf) {
7967 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7968 conn->inbufused = strlen(conn->inbuf);
7971 /* Received a full Header? */
7972 sip->processing_input = TRUE;
7973 while (sip->processing_input &&
7974 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7975 time_t currtime = time(NULL);
7976 cur += 2;
7977 cur[0] = '\0';
7978 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7979 g_free(tmp);
7980 msg = sipmsg_parse_header(conn->inbuf);
7981 cur[0] = '\r';
7982 cur += 2;
7983 restlen = conn->inbufused - (cur - conn->inbuf);
7984 if (msg && restlen >= msg->bodylen) {
7985 dummy = g_malloc(msg->bodylen + 1);
7986 memcpy(dummy, cur, msg->bodylen);
7987 dummy[msg->bodylen] = '\0';
7988 msg->body = dummy;
7989 cur += msg->bodylen;
7990 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7991 conn->inbufused = strlen(conn->inbuf);
7992 } else {
7993 if (msg){
7994 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7995 sipmsg_free(msg);
7997 return;
8000 /*if (msg->body) {
8001 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8004 // Verify the signature before processing it
8005 if (sip->registrar.gssapi_context) {
8006 struct sipmsg_breakdown msgbd;
8007 gchar *signature_input_str;
8008 gchar *rspauth;
8009 msgbd.msg = msg;
8010 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8011 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8013 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8015 if (rspauth != NULL) {
8016 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8017 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8018 process_input_message(sip, msg);
8019 } else {
8020 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8021 purple_connection_error(sip->gc, _("Invalid message signature received"));
8022 sip->gc->wants_to_die = TRUE;
8024 } else if (msg->response == 401) {
8025 purple_connection_error(sip->gc, _("Authentication failed"));
8026 sip->gc->wants_to_die = TRUE;
8028 g_free(signature_input_str);
8030 g_free(rspauth);
8031 sipmsg_breakdown_free(&msgbd);
8032 } else {
8033 process_input_message(sip, msg);
8036 sipmsg_free(msg);
8040 static void sipe_udp_process(gpointer data, gint source,
8041 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8043 PurpleConnection *gc = data;
8044 struct sipe_account_data *sip = gc->proto_data;
8045 int len;
8047 static char buffer[65536];
8048 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8049 time_t currtime = time(NULL);
8050 struct sipmsg *msg;
8051 buffer[len] = '\0';
8052 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8053 msg = sipmsg_parse_msg(buffer);
8054 if (msg) process_input_message(sip, msg);
8058 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8060 struct sipe_account_data *sip = gc->proto_data;
8061 PurpleSslConnection *gsc = sip->gsc;
8063 SIPE_DEBUG_ERROR("%s", debug);
8064 purple_connection_error(gc, msg);
8066 /* Invalidate this connection. Next send will open a new one */
8067 if (gsc) {
8068 connection_remove(sip, gsc->fd);
8069 purple_ssl_close(gsc);
8071 sip->gsc = NULL;
8072 sip->fd = -1;
8075 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8076 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8078 PurpleConnection *gc = data;
8079 struct sipe_account_data *sip;
8080 struct sip_connection *conn;
8081 int readlen, len;
8082 gboolean firstread = TRUE;
8084 /* NOTE: This check *IS* necessary */
8085 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8086 purple_ssl_close(gsc);
8087 return;
8090 sip = gc->proto_data;
8091 conn = connection_find(sip, gsc->fd);
8092 if (conn == NULL) {
8093 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8094 gc->wants_to_die = TRUE;
8095 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8096 return;
8099 /* Read all available data from the SSL connection */
8100 do {
8101 /* Increase input buffer size as needed */
8102 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8103 conn->inbuflen += SIMPLE_BUF_INC;
8104 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8105 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8108 /* Try to read as much as there is space left in the buffer */
8109 readlen = conn->inbuflen - conn->inbufused - 1;
8110 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8112 if (len < 0 && errno == EAGAIN) {
8113 /* Try again later */
8114 return;
8115 } else if (len < 0) {
8116 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8117 return;
8118 } else if (firstread && (len == 0)) {
8119 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8120 return;
8123 conn->inbufused += len;
8124 firstread = FALSE;
8126 /* Equivalence indicates that there is possibly more data to read */
8127 } while (len == readlen);
8129 conn->inbuf[conn->inbufused] = '\0';
8130 process_input(sip, conn);
8134 static void sipe_input_cb(gpointer data, gint source,
8135 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8137 PurpleConnection *gc = data;
8138 struct sipe_account_data *sip = gc->proto_data;
8139 int len;
8140 struct sip_connection *conn = connection_find(sip, source);
8141 if (!conn) {
8142 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8143 return;
8146 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8147 conn->inbuflen += SIMPLE_BUF_INC;
8148 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8151 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8153 if (len < 0 && errno == EAGAIN)
8154 return;
8155 else if (len <= 0) {
8156 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8157 connection_remove(sip, source);
8158 if (sip->fd == source) sip->fd = -1;
8159 return;
8162 conn->inbufused += len;
8163 conn->inbuf[conn->inbufused] = '\0';
8165 process_input(sip, conn);
8168 /* Callback for new connections on incoming TCP port */
8169 static void sipe_newconn_cb(gpointer data, gint source,
8170 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8172 PurpleConnection *gc = data;
8173 struct sipe_account_data *sip = gc->proto_data;
8174 struct sip_connection *conn;
8176 int newfd = accept(source, NULL, NULL);
8178 conn = connection_create(sip, newfd);
8180 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8183 static void login_cb(gpointer data, gint source,
8184 SIPE_UNUSED_PARAMETER const gchar *error_message)
8186 PurpleConnection *gc = data;
8187 struct sipe_account_data *sip;
8188 struct sip_connection *conn;
8190 if (!PURPLE_CONNECTION_IS_VALID(gc))
8192 if (source >= 0)
8193 close(source);
8194 return;
8197 if (source < 0) {
8198 purple_connection_error(gc, _("Could not connect"));
8199 return;
8202 sip = gc->proto_data;
8203 sip->fd = source;
8204 sip->last_keepalive = time(NULL);
8206 conn = connection_create(sip, source);
8208 do_register(sip);
8210 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8213 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8214 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8216 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8217 if (sip == NULL) return;
8219 do_register(sip);
8222 static guint sipe_ht_hash_nick(const char *nick)
8224 char *lc = g_utf8_strdown(nick, -1);
8225 guint bucket = g_str_hash(lc);
8226 g_free(lc);
8228 return bucket;
8231 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8233 char *nick1_norm = NULL;
8234 char *nick2_norm = NULL;
8235 gboolean equal;
8237 if (nick1 == NULL && nick2 == NULL) return TRUE;
8238 if (nick1 == NULL || nick2 == NULL ||
8239 !g_utf8_validate(nick1, -1, NULL) ||
8240 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8242 nick1_norm = g_utf8_casefold(nick1, -1);
8243 nick2_norm = g_utf8_casefold(nick2, -1);
8244 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8245 g_free(nick2_norm);
8246 g_free(nick1_norm);
8248 return equal;
8251 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8253 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8255 sip->listen_data = NULL;
8257 if (listenfd == -1) {
8258 purple_connection_error(sip->gc, _("Could not create listen socket"));
8259 return;
8262 sip->fd = listenfd;
8264 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8265 sip->listenfd = sip->fd;
8267 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8269 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8270 do_register(sip);
8273 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8274 SIPE_UNUSED_PARAMETER const char *error_message)
8276 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8278 sip->query_data = NULL;
8280 if (!hosts || !hosts->data) {
8281 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8282 return;
8285 hosts = g_slist_remove(hosts, hosts->data);
8286 g_free(sip->serveraddr);
8287 sip->serveraddr = hosts->data;
8288 hosts = g_slist_remove(hosts, hosts->data);
8289 while (hosts) {
8290 void *tmp = hosts->data;
8291 hosts = g_slist_remove(hosts, tmp);
8292 hosts = g_slist_remove(hosts, tmp);
8293 g_free(tmp);
8296 /* create socket for incoming connections */
8297 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8298 sipe_udp_host_resolved_listen_cb, sip);
8299 if (sip->listen_data == NULL) {
8300 purple_connection_error(sip->gc, _("Could not create listen socket"));
8301 return;
8305 static const struct sipe_service_data *current_service = NULL;
8307 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8308 PurpleSslErrorType error,
8309 gpointer data)
8311 PurpleConnection *gc = data;
8312 struct sipe_account_data *sip;
8314 /* If the connection is already disconnected, we don't need to do anything else */
8315 if (!PURPLE_CONNECTION_IS_VALID(gc))
8316 return;
8318 sip = gc->proto_data;
8319 current_service = sip->service_data;
8320 if (current_service) {
8321 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8322 current_service->transport ? current_service->transport : "NULL",
8323 current_service->service ? current_service->service : "NULL");
8326 sip->fd = -1;
8327 sip->gsc = NULL;
8329 switch(error) {
8330 case PURPLE_SSL_CONNECT_FAILED:
8331 purple_connection_error(gc, _("Connection failed"));
8332 break;
8333 case PURPLE_SSL_HANDSHAKE_FAILED:
8334 purple_connection_error(gc, _("SSL handshake failed"));
8335 break;
8336 case PURPLE_SSL_CERTIFICATE_INVALID:
8337 purple_connection_error(gc, _("SSL certificate invalid"));
8338 break;
8342 static void
8343 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8345 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8346 PurpleProxyConnectData *connect_data;
8348 sip->listen_data = NULL;
8350 sip->listenfd = listenfd;
8351 if (sip->listenfd == -1) {
8352 purple_connection_error(sip->gc, _("Could not create listen socket"));
8353 return;
8356 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8357 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8358 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8359 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8360 sipe_newconn_cb, sip->gc);
8361 SIPE_DEBUG_INFO("connecting to %s port %d",
8362 sip->realhostname, sip->realport);
8363 /* open tcp connection to the server */
8364 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8365 sip->realport, login_cb, sip->gc);
8367 if (connect_data == NULL) {
8368 purple_connection_error(sip->gc, _("Could not create socket"));
8372 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8374 PurpleAccount *account = sip->account;
8375 PurpleConnection *gc = sip->gc;
8377 if (port == 0) {
8378 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8381 sip->realhostname = hostname;
8382 sip->realport = port;
8384 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8385 hostname, port);
8387 /* TODO: is there a good default grow size? */
8388 if (sip->transport != SIPE_TRANSPORT_UDP)
8389 sip->txbuf = purple_circ_buffer_new(0);
8391 if (sip->transport == SIPE_TRANSPORT_TLS) {
8392 /* SSL case */
8393 if (!purple_ssl_is_supported()) {
8394 gc->wants_to_die = TRUE;
8395 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8396 return;
8399 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8401 sip->gsc = purple_ssl_connect(account, hostname, port,
8402 login_cb_ssl, sipe_ssl_connect_failure, gc);
8403 if (sip->gsc == NULL) {
8404 purple_connection_error(gc, _("Could not create SSL context"));
8405 return;
8407 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8408 /* UDP case */
8409 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8411 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8412 if (sip->query_data == NULL) {
8413 purple_connection_error(gc, _("Could not resolve hostname"));
8415 } else {
8416 /* TCP case */
8417 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8418 /* create socket for incoming connections */
8419 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8420 sipe_tcp_connect_listen_cb, sip);
8421 if (sip->listen_data == NULL) {
8422 purple_connection_error(gc, _("Could not create listen socket"));
8423 return;
8428 /* Service list for autodection */
8429 static const struct sipe_service_data service_autodetect[] = {
8430 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8431 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8432 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8433 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8434 { NULL, NULL, 0 }
8437 /* Service list for SSL/TLS */
8438 static const struct sipe_service_data service_tls[] = {
8439 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8440 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8441 { NULL, NULL, 0 }
8444 /* Service list for TCP */
8445 static const struct sipe_service_data service_tcp[] = {
8446 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8447 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8448 { NULL, NULL, 0 }
8451 /* Service list for UDP */
8452 static const struct sipe_service_data service_udp[] = {
8453 { "sip", "udp", SIPE_TRANSPORT_UDP },
8454 { NULL, NULL, 0 }
8457 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8458 static void resolve_next_service(struct sipe_account_data *sip,
8459 const struct sipe_service_data *start)
8461 if (start) {
8462 sip->service_data = start;
8463 } else {
8464 sip->service_data++;
8465 if (sip->service_data->service == NULL) {
8466 gchar *hostname;
8467 /* Try connecting to the SIP hostname directly */
8468 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8469 if (sip->auto_transport) {
8470 // If SSL is supported, default to using it; OCS servers aren't configured
8471 // by default to accept TCP
8472 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8473 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8474 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8477 hostname = g_strdup(sip->sipdomain);
8478 create_connection(sip, hostname, 0);
8479 return;
8483 /* Try to resolve next service */
8484 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8485 sip->service_data->transport,
8486 sip->sipdomain,
8487 srvresolved, sip);
8490 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8492 struct sipe_account_data *sip = data;
8494 sip->srv_query_data = NULL;
8496 /* find the host to connect to */
8497 if (results) {
8498 gchar *hostname = g_strdup(resp->hostname);
8499 int port = resp->port;
8500 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8501 hostname, port);
8502 g_free(resp);
8504 sip->transport = sip->service_data->type;
8506 create_connection(sip, hostname, port);
8507 } else {
8508 resolve_next_service(sip, NULL);
8512 static void sipe_login(PurpleAccount *account)
8514 PurpleConnection *gc;
8515 struct sipe_account_data *sip;
8516 gchar **signinname_login, **userserver;
8517 const char *transport;
8518 const char *email;
8520 const char *username = purple_account_get_username(account);
8521 gc = purple_account_get_connection(account);
8523 SIPE_DEBUG_INFO("sipe_login: username '%s'", username);
8525 if (strpbrk(username, "\t\v\r\n") != NULL) {
8526 gc->wants_to_die = TRUE;
8527 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8528 return;
8531 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8532 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8533 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8534 sip->gc = gc;
8535 sip->account = account;
8536 sip->reregister_set = FALSE;
8537 sip->reauthenticate_set = FALSE;
8538 sip->subscribed = FALSE;
8539 sip->subscribed_buddies = FALSE;
8540 sip->initial_state_published = FALSE;
8542 /* username format: <username>,[<optional login>] */
8543 signinname_login = g_strsplit(username, ",", 2);
8544 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login[0]);
8546 /* ensure that username format is name@domain */
8547 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8548 g_strfreev(signinname_login);
8549 gc->wants_to_die = TRUE;
8550 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8551 return;
8553 sip->username = g_strdup(signinname_login[0]);
8555 /* ensure that email format is name@domain if provided */
8556 email = purple_account_get_string(sip->account, "email", NULL);
8557 if (!is_empty(email) &&
8558 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8560 gc->wants_to_die = TRUE;
8561 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8562 return;
8564 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8566 /* login name specified? */
8567 if (signinname_login[1] && strlen(signinname_login[1])) {
8568 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8569 gboolean has_domain = domain_user[1] != NULL;
8570 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login[1]);
8571 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8572 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8573 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8574 sip->authdomain ? sip->authdomain : "", sip->authuser);
8575 g_strfreev(domain_user);
8578 userserver = g_strsplit(signinname_login[0], "@", 2);
8579 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver[0], userserver[1]);
8580 purple_connection_set_display_name(gc, userserver[0]);
8581 sip->sipdomain = g_strdup(userserver[1]);
8582 g_strfreev(userserver);
8583 g_strfreev(signinname_login);
8585 if (strchr(sip->username, ' ') != NULL) {
8586 gc->wants_to_die = TRUE;
8587 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8588 return;
8591 sip->password = g_strdup(purple_connection_get_password(gc));
8593 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8594 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8595 g_free, (GDestroyNotify)g_hash_table_destroy);
8596 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8597 g_free, (GDestroyNotify)sipe_subscription_free);
8599 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8601 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8603 g_free(sip->status);
8604 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8606 sip->auto_transport = FALSE;
8607 transport = purple_account_get_string(account, "transport", "auto");
8608 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8609 if (userserver[0]) {
8610 /* Use user specified server[:port] */
8611 int port = 0;
8613 if (userserver[1])
8614 port = atoi(userserver[1]);
8616 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
8617 userserver[0], port);
8619 if (sipe_strequal(transport, "auto")) {
8620 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8621 } else if (sipe_strequal(transport, "tls")) {
8622 sip->transport = SIPE_TRANSPORT_TLS;
8623 } else if (sipe_strequal(transport, "tcp")) {
8624 sip->transport = SIPE_TRANSPORT_TCP;
8625 } else {
8626 sip->transport = SIPE_TRANSPORT_UDP;
8629 create_connection(sip, g_strdup(userserver[0]), port);
8630 } else {
8631 /* Server auto-discovery */
8632 if (sipe_strequal(transport, "auto")) {
8633 sip->auto_transport = TRUE;
8634 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8635 current_service++;
8636 resolve_next_service(sip, current_service);
8637 } else {
8638 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8640 } else if (sipe_strequal(transport, "tls")) {
8641 resolve_next_service(sip, service_tls);
8642 } else if (sipe_strequal(transport, "tcp")) {
8643 resolve_next_service(sip, service_tcp);
8644 } else {
8645 resolve_next_service(sip, service_udp);
8648 g_strfreev(userserver);
8651 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8653 connection_free_all(sip);
8655 g_free(sip->epid);
8656 sip->epid = NULL;
8658 if (sip->query_data != NULL)
8659 purple_dnsquery_destroy(sip->query_data);
8660 sip->query_data = NULL;
8662 if (sip->srv_query_data != NULL)
8663 purple_srv_cancel(sip->srv_query_data);
8664 sip->srv_query_data = NULL;
8666 if (sip->listen_data != NULL)
8667 purple_network_listen_cancel(sip->listen_data);
8668 sip->listen_data = NULL;
8670 if (sip->gsc != NULL)
8671 purple_ssl_close(sip->gsc);
8672 sip->gsc = NULL;
8674 sipe_auth_free(&sip->registrar);
8675 sipe_auth_free(&sip->proxy);
8677 if (sip->txbuf)
8678 purple_circ_buffer_destroy(sip->txbuf);
8679 sip->txbuf = NULL;
8681 g_free(sip->realhostname);
8682 sip->realhostname = NULL;
8684 g_free(sip->server_version);
8685 sip->server_version = NULL;
8687 if (sip->listenpa)
8688 purple_input_remove(sip->listenpa);
8689 sip->listenpa = 0;
8690 if (sip->tx_handler)
8691 purple_input_remove(sip->tx_handler);
8692 sip->tx_handler = 0;
8693 if (sip->resendtimeout)
8694 purple_timeout_remove(sip->resendtimeout);
8695 sip->resendtimeout = 0;
8696 if (sip->timeouts) {
8697 GSList *entry = sip->timeouts;
8698 while (entry) {
8699 struct scheduled_action *sched_action = entry->data;
8700 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
8701 purple_timeout_remove(sched_action->timeout_handler);
8702 if (sched_action->destroy) {
8703 (*sched_action->destroy)(sched_action->payload);
8705 g_free(sched_action->name);
8706 g_free(sched_action);
8707 entry = entry->next;
8710 g_slist_free(sip->timeouts);
8712 if (sip->allow_events) {
8713 GSList *entry = sip->allow_events;
8714 while (entry) {
8715 g_free(entry->data);
8716 entry = entry->next;
8719 g_slist_free(sip->allow_events);
8721 if (sip->containers) {
8722 GSList *entry = sip->containers;
8723 while (entry) {
8724 free_container((struct sipe_container *)entry->data);
8725 entry = entry->next;
8728 g_slist_free(sip->containers);
8730 if (sip->contact)
8731 g_free(sip->contact);
8732 sip->contact = NULL;
8733 if (sip->regcallid)
8734 g_free(sip->regcallid);
8735 sip->regcallid = NULL;
8737 if (sip->serveraddr)
8738 g_free(sip->serveraddr);
8739 sip->serveraddr = NULL;
8741 if (sip->focus_factory_uri)
8742 g_free(sip->focus_factory_uri);
8743 sip->focus_factory_uri = NULL;
8745 sip->fd = -1;
8746 sip->processing_input = FALSE;
8748 if (sip->ews) {
8749 sipe_ews_free(sip->ews);
8751 sip->ews = NULL;
8755 * A callback for g_hash_table_foreach_remove
8757 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8758 SIPE_UNUSED_PARAMETER gpointer user_data)
8760 sipe_free_buddy((struct sipe_buddy *) buddy);
8762 /* We must return TRUE as the key/value have already been deleted */
8763 return(TRUE);
8766 static void sipe_close(PurpleConnection *gc)
8768 struct sipe_account_data *sip = gc->proto_data;
8770 if (sip) {
8771 /* leave all conversations */
8772 sipe_session_close_all(sip);
8773 sipe_session_remove_all(sip);
8775 if (sip->csta) {
8776 sip_csta_close(sip);
8779 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8780 /* unsubscribe all */
8781 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8783 /* unregister */
8784 do_register_exp(sip, 0);
8787 sipe_connection_cleanup(sip);
8788 g_free(sip->sipdomain);
8789 g_free(sip->username);
8790 g_free(sip->email);
8791 g_free(sip->password);
8792 g_free(sip->authdomain);
8793 g_free(sip->authuser);
8794 g_free(sip->status);
8795 g_free(sip->note);
8796 g_free(sip->user_states);
8798 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8799 g_hash_table_destroy(sip->buddies);
8800 g_hash_table_destroy(sip->our_publications);
8801 g_hash_table_destroy(sip->user_state_publications);
8802 g_hash_table_destroy(sip->subscriptions);
8803 g_hash_table_destroy(sip->filetransfers);
8805 if (sip->groups) {
8806 GSList *entry = sip->groups;
8807 while (entry) {
8808 struct sipe_group *group = entry->data;
8809 g_free(group->name);
8810 g_free(group);
8811 entry = entry->next;
8814 g_slist_free(sip->groups);
8816 if (sip->our_publication_keys) {
8817 GSList *entry = sip->our_publication_keys;
8818 while (entry) {
8819 g_free(entry->data);
8820 entry = entry->next;
8823 g_slist_free(sip->our_publication_keys);
8825 while (sip->transactions)
8826 transactions_remove(sip, sip->transactions->data);
8828 g_free(gc->proto_data);
8829 gc->proto_data = NULL;
8832 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8833 SIPE_UNUSED_PARAMETER void *user_data)
8835 PurpleAccount *acct = purple_connection_get_account(gc);
8836 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8837 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8838 if (conv == NULL)
8839 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8840 purple_conversation_present(conv);
8841 g_free(id);
8844 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8845 SIPE_UNUSED_PARAMETER void *user_data)
8848 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8849 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8852 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8853 SIPE_UNUSED_PARAMETER struct transaction *trans)
8855 PurpleNotifySearchResults *results;
8856 PurpleNotifySearchColumn *column;
8857 xmlnode *searchResults;
8858 xmlnode *mrow;
8859 int match_count = 0;
8860 gboolean more = FALSE;
8861 gchar *secondary;
8863 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
8865 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8866 if (!searchResults) {
8867 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
8868 return FALSE;
8871 results = purple_notify_searchresults_new();
8873 if (results == NULL) {
8874 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
8875 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8877 xmlnode_free(searchResults);
8878 return FALSE;
8881 column = purple_notify_searchresults_column_new(_("User name"));
8882 purple_notify_searchresults_column_add(results, column);
8884 column = purple_notify_searchresults_column_new(_("Name"));
8885 purple_notify_searchresults_column_add(results, column);
8887 column = purple_notify_searchresults_column_new(_("Company"));
8888 purple_notify_searchresults_column_add(results, column);
8890 column = purple_notify_searchresults_column_new(_("Country"));
8891 purple_notify_searchresults_column_add(results, column);
8893 column = purple_notify_searchresults_column_new(_("Email"));
8894 purple_notify_searchresults_column_add(results, column);
8896 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8897 GList *row = NULL;
8899 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8900 row = g_list_append(row, g_strdup(uri_parts[1]));
8901 g_strfreev(uri_parts);
8903 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8904 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8905 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8906 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8908 purple_notify_searchresults_row_add(results, row);
8909 match_count++;
8912 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8913 char *data = xmlnode_get_data(mrow);
8914 more = (g_strcasecmp(data, "true") == 0);
8915 g_free(data);
8918 secondary = g_strdup_printf(
8919 dngettext(PACKAGE_NAME,
8920 "Found %d contact%s:",
8921 "Found %d contacts%s:", match_count),
8922 match_count, more ? _(" (more matched your query)") : "");
8924 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8925 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8926 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8928 g_free(secondary);
8929 xmlnode_free(searchResults);
8930 return TRUE;
8933 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8935 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8936 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8937 unsigned i = 0;
8939 if (!attrs) return;
8941 do {
8942 PurpleRequestField *field = entries->data;
8943 const char *id = purple_request_field_get_id(field);
8944 const char *value = purple_request_field_string_get_value(field);
8946 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
8948 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8949 } while ((entries = g_list_next(entries)) != NULL);
8950 attrs[i] = NULL;
8952 if (i > 0) {
8953 struct sipe_account_data *sip = gc->proto_data;
8954 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8955 gchar *query = g_strjoinv(NULL, attrs);
8956 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8957 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
8958 send_soap_request_with_cb(sip, domain_uri, body,
8959 (TransCallback) process_search_contact_response, NULL);
8960 g_free(domain_uri);
8961 g_free(body);
8962 g_free(query);
8965 g_strfreev(attrs);
8968 static void sipe_show_find_contact(PurplePluginAction *action)
8970 PurpleConnection *gc = (PurpleConnection *) action->context;
8971 PurpleRequestFields *fields;
8972 PurpleRequestFieldGroup *group;
8973 PurpleRequestField *field;
8975 fields = purple_request_fields_new();
8976 group = purple_request_field_group_new(NULL);
8977 purple_request_fields_add_group(fields, group);
8979 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8980 purple_request_field_group_add_field(group, field);
8981 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8982 purple_request_field_group_add_field(group, field);
8983 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8984 purple_request_field_group_add_field(group, field);
8985 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8986 purple_request_field_group_add_field(group, field);
8988 purple_request_fields(gc,
8989 _("Search"),
8990 _("Search for a contact"),
8991 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8992 fields,
8993 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8994 _("_Cancel"), NULL,
8995 purple_connection_get_account(gc), NULL, NULL, gc);
8998 static void sipe_show_about_plugin(PurplePluginAction *action)
9000 PurpleConnection *gc = (PurpleConnection *) action->context;
9001 char *tmp = g_strdup_printf(
9003 * Non-translatable parts, like markup, are hard-coded
9004 * into the format string. This requires more translatable
9005 * texts but it makes the translations less error prone.
9007 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
9008 "<br/>"
9009 /* 1 */ "%s:<br/>"
9010 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9011 "<li> - MS Office Communications Server 2007</li><br/>"
9012 "<li> - MS Live Communications Server 2005</li><br/>"
9013 "<li> - MS Live Communications Server 2003</li><br/>"
9014 "<li> - Reuters Messaging</li><br/>"
9015 "<br/>"
9016 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
9017 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9018 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9019 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9020 /* 8 */ "%s: GPLv2+<br/>"
9021 "<br/>"
9022 /* 9 */ "%s:<br/>"
9023 " - CERN<br/>"
9024 " - Reuters Messaging network<br/>"
9025 " - Deutsche Bank<br/>"
9026 " - Merrill Lynch<br/>"
9027 " - Wachovia<br/>"
9028 " - Intel<br/>"
9029 " - Nokia<br/>"
9030 " - HP<br/>"
9031 " - Symantec<br/>"
9032 " - Accenture<br/>"
9033 " - Capgemini<br/>"
9034 " - Siemens<br/>"
9035 " - Alcatel-Lucent<br/>"
9036 " - BT<br/>"
9037 "<br/>"
9038 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9039 "<br/>"
9040 /* 12 */ "<b>%s:</b><br/>"
9041 " - Anibal Avelar<br/>"
9042 " - Gabriel Burt<br/>"
9043 " - Stefan Becker<br/>"
9044 " - pier11<br/>"
9045 " - Jakub Adam<br/>"
9046 " - Tomáš Hrabčík<br/>"
9047 "<br/>"
9048 /* 13 */ "%s<br/>"
9050 /* The next 13 texts make up the SIPE about note text */
9051 /* About note, part 1/13: introduction */
9052 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9053 /* About note, part 2/13: home page URL (label) */
9054 _("Home"),
9055 /* About note, part 3/13: support forum URL (label) */
9056 _("Support"),
9057 /* About note, part 4/13: support forum name (hyperlink text) */
9058 _("Help Forum"),
9059 /* About note, part 5/13: bug tracker URL (label) */
9060 _("Report Problems"),
9061 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9062 _("Bug Tracker"),
9063 /* About note, part 7/13: translation service URL (label) */
9064 _("Translations"),
9065 /* About note, part 8/13: license type (label) */
9066 _("License"),
9067 /* About note, part 9/13: known users */
9068 _("We support users in such organizations as"),
9069 /* About note, part 10/13: translation request, text before Transifex.net URL */
9070 /* append a space if text is not empty */
9071 _("Please help us to translate SIPE to your native language here at "),
9072 /* About note, part 11/13: translation request, text after Transifex.net URL */
9073 /* start with a space if text is not empty */
9074 _(" using convenient web interface"),
9075 /* About note, part 12/13: author list (header) */
9076 _("Authors"),
9077 /* About note, part 13/13: Localization credit */
9078 /* PLEASE NOTE: do *NOT* simply translate the english original */
9079 /* but write something similar to the following sentence: */
9080 /* "Localization for <language name> (<language code>): <name>" */
9081 _("Original texts in English (en): SIPE developers")
9083 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9084 g_free(tmp);
9087 static void sipe_republish_calendar(PurplePluginAction *action)
9089 PurpleConnection *gc = (PurpleConnection *) action->context;
9090 struct sipe_account_data *sip = gc->proto_data;
9092 sipe_update_calendar(sip);
9095 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9096 gpointer value,
9097 GString* str)
9099 struct sipe_publication *publication = value;
9101 g_string_append_printf( str,
9102 SIPE_PUB_XML_PUBLICATION_CLEAR,
9103 publication->category,
9104 publication->instance,
9105 publication->container,
9106 publication->version,
9107 "static");
9110 static void sipe_reset_status(PurplePluginAction *action)
9112 PurpleConnection *gc = (PurpleConnection *) action->context;
9113 struct sipe_account_data *sip = gc->proto_data;
9115 if (sip->ocs2007) /* 2007+ */
9117 GString* str = g_string_new(NULL);
9118 gchar *publications;
9120 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9121 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9122 return;
9125 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9126 publications = g_string_free(str, FALSE);
9128 send_presence_publish(sip, publications);
9129 g_free(publications);
9131 else /* 2005 */
9133 send_presence_soap0(sip, FALSE, TRUE);
9137 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9138 gpointer context)
9140 PurpleConnection *gc = (PurpleConnection *)context;
9141 struct sipe_account_data *sip = gc->proto_data;
9142 GList *menu = NULL;
9143 PurplePluginAction *act;
9144 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9146 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9147 menu = g_list_prepend(menu, act);
9149 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9150 menu = g_list_prepend(menu, act);
9152 if (sipe_strequal(calendar, "EXCH")) {
9153 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9154 menu = g_list_prepend(menu, act);
9157 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9158 menu = g_list_prepend(menu, act);
9160 menu = g_list_reverse(menu);
9162 return menu;
9165 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9169 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9171 return TRUE;
9175 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9177 return TRUE;
9181 static char *sipe_status_text(PurpleBuddy *buddy)
9183 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9184 const PurpleStatus *status = purple_presence_get_active_status(presence);
9185 const char *status_id = purple_status_get_id(status);
9186 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9187 struct sipe_buddy *sbuddy;
9188 char *text = NULL;
9190 if (!sip) return NULL; /* happens on pidgin exit */
9192 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9193 if (sbuddy) {
9194 const char *activity_str = sbuddy->activity ?
9195 sbuddy->activity :
9196 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9197 purple_status_get_name(status) : NULL;
9199 if (activity_str && sbuddy->note)
9201 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9203 else if (activity_str)
9205 text = g_strdup(activity_str);
9207 else if (sbuddy->note)
9209 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9213 return text;
9216 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9218 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9219 const PurpleStatus *status = purple_presence_get_active_status(presence);
9220 struct sipe_account_data *sip;
9221 struct sipe_buddy *sbuddy;
9222 char *note = NULL;
9223 gboolean is_oof_note = FALSE;
9224 char *activity = NULL;
9225 char *calendar = NULL;
9226 char *meeting_subject = NULL;
9227 char *meeting_location = NULL;
9229 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9230 if (sip) //happens on pidgin exit
9232 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9233 if (sbuddy)
9235 note = sbuddy->note;
9236 is_oof_note = sbuddy->is_oof_note;
9237 activity = sbuddy->activity;
9238 calendar = sipe_cal_get_description(sbuddy);
9239 meeting_subject = sbuddy->meeting_subject;
9240 meeting_location = sbuddy->meeting_location;
9244 //Layout
9245 if (purple_presence_is_online(presence))
9247 const char *status_str = activity ? activity : purple_status_get_name(status);
9249 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9251 if (purple_presence_is_online(presence) &&
9252 !is_empty(calendar))
9254 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9256 g_free(calendar);
9257 if (!is_empty(meeting_location))
9259 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9261 if (!is_empty(meeting_subject))
9263 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9266 if (note)
9268 char *tmp = g_strdup_printf("<i>%s</i>", note);
9269 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy->name, note);
9271 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9272 g_free(tmp);
9277 #if PURPLE_VERSION_CHECK(2,5,0)
9278 static GHashTable *
9279 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9281 GHashTable *table;
9282 table = g_hash_table_new(g_str_hash, g_str_equal);
9283 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9284 return table;
9286 #endif
9288 static PurpleBuddy *
9289 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9291 PurpleBuddy *clone;
9292 const gchar *server_alias, *email;
9293 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9295 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9297 purple_blist_add_buddy(clone, NULL, group, NULL);
9299 server_alias = purple_buddy_get_server_alias(buddy);
9300 if (server_alias) {
9301 purple_blist_server_alias_buddy(clone, server_alias);
9304 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9305 if (email) {
9306 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9309 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9310 //for UI to update;
9311 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9312 return clone;
9315 static void
9316 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9318 PurpleBuddy *buddy, *b;
9319 PurpleConnection *gc;
9320 PurpleGroup * group = purple_find_group(group_name);
9322 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9324 buddy = (PurpleBuddy *)node;
9326 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9327 gc = purple_account_get_connection(buddy->account);
9329 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9330 if (!b){
9331 purple_blist_add_buddy_clone(group, buddy);
9334 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9337 static void
9338 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9340 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9342 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9344 /* 2007+ conference */
9345 if (sip->ocs2007)
9347 sipe_conf_add(sip, buddy->name);
9349 else /* 2005- multiparty chat */
9351 gchar *self = sip_uri_self(sip);
9352 struct sip_session *session;
9354 session = sipe_session_add_chat(sip);
9355 session->chat_title = sipe_chat_get_name(session->callid);
9356 session->roster_manager = g_strdup(self);
9358 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9359 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9360 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9361 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9363 g_free(self);
9367 static gboolean
9368 sipe_is_election_finished(struct sip_session *session)
9370 gboolean res = TRUE;
9372 SIPE_DIALOG_FOREACH {
9373 if (dialog->election_vote == 0) {
9374 res = FALSE;
9375 break;
9377 } SIPE_DIALOG_FOREACH_END;
9379 if (res) {
9380 session->is_voting_in_progress = FALSE;
9382 return res;
9385 static void
9386 sipe_election_start(struct sipe_account_data *sip,
9387 struct sip_session *session)
9389 int election_timeout;
9391 if (session->is_voting_in_progress) {
9392 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9393 return;
9394 } else {
9395 session->is_voting_in_progress = TRUE;
9397 session->bid = rand();
9399 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9401 SIPE_DIALOG_FOREACH {
9402 /* reset election_vote for each chat participant */
9403 dialog->election_vote = 0;
9405 /* send RequestRM to each chat participant*/
9406 sipe_send_election_request_rm(sip, dialog, session->bid);
9407 } SIPE_DIALOG_FOREACH_END;
9409 election_timeout = 15; /* sec */
9410 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9414 * @param who a URI to whom to invite to chat
9416 void
9417 sipe_invite_to_chat(struct sipe_account_data *sip,
9418 struct sip_session *session,
9419 const gchar *who)
9421 /* a conference */
9422 if (session->focus_uri)
9424 sipe_invite_conf(sip, session, who);
9426 else /* a multi-party chat */
9428 gchar *self = sip_uri_self(sip);
9429 if (session->roster_manager) {
9430 if (sipe_strcase_equal(session->roster_manager, self)) {
9431 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9432 } else {
9433 sipe_refer(sip, session, who);
9435 } else {
9436 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9438 session->pending_invite_queue = slist_insert_unique_sorted(
9439 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9441 sipe_election_start(sip, session);
9443 g_free(self);
9447 void
9448 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9449 struct sip_session *session)
9451 gchar *invitee;
9452 GSList *entry = session->pending_invite_queue;
9454 while (entry) {
9455 invitee = entry->data;
9456 sipe_invite_to_chat(sip, session, invitee);
9457 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9458 g_free(invitee);
9462 static void
9463 sipe_election_result(struct sipe_account_data *sip,
9464 void *sess)
9466 struct sip_session *session = (struct sip_session *)sess;
9467 gchar *rival;
9468 gboolean has_won = TRUE;
9470 if (session->roster_manager) {
9471 SIPE_DEBUG_INFO(
9472 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9473 session->roster_manager);
9474 return;
9477 session->is_voting_in_progress = FALSE;
9479 SIPE_DIALOG_FOREACH {
9480 if (dialog->election_vote < 0) {
9481 has_won = FALSE;
9482 rival = dialog->with;
9483 break;
9485 } SIPE_DIALOG_FOREACH_END;
9487 if (has_won) {
9488 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9490 session->roster_manager = sip_uri_self(sip);
9492 SIPE_DIALOG_FOREACH {
9493 /* send SetRM to each chat participant*/
9494 sipe_send_election_set_rm(sip, dialog);
9495 } SIPE_DIALOG_FOREACH_END;
9496 } else {
9497 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9499 session->bid = 0;
9501 sipe_process_pending_invite_queue(sip, session);
9505 * For 2007+ conference only.
9507 static void
9508 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9510 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9511 struct sip_session *session;
9513 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9514 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9516 session = sipe_session_find_chat_by_title(sip, chat_title);
9518 sipe_conf_modify_user_role(sip, session, buddy->name);
9522 * For 2007+ conference only.
9524 static void
9525 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9527 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9528 struct sip_session *session;
9530 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9531 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9533 session = sipe_session_find_chat_by_title(sip, chat_title);
9535 sipe_conf_delete_user(sip, session, buddy->name);
9538 static void
9539 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9541 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9542 struct sip_session *session;
9544 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9545 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9547 session = sipe_session_find_chat_by_title(sip, chat_title);
9549 sipe_invite_to_chat(sip, session, buddy->name);
9552 static void
9553 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9555 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9557 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9558 if (phone) {
9559 char *tel_uri = sip_to_tel_uri(phone);
9561 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9562 sip_csta_make_call(sip, tel_uri);
9564 g_free(tel_uri);
9568 static void
9569 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9571 const gchar *email;
9572 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9574 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9575 if (email)
9577 char *mailto = g_strdup_printf("mailto:%s", email);
9578 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s", email);
9579 #ifndef _WIN32
9581 pid_t pid;
9582 char *const parmList[] = {"xdg-email", mailto, NULL};
9583 if ((pid = fork()) == -1)
9585 SIPE_DEBUG_INFO_NOFORMAT("fork() error");
9587 else if (pid == 0)
9589 execvp(parmList[0], parmList);
9590 SIPE_DEBUG_INFO_NOFORMAT("Return not expected. Must be an execvp() error.");
9593 #else
9595 BOOL ret;
9596 _flushall();
9597 errno = 0;
9598 //@TODO resolve env variable %WINDIR% first
9599 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9600 if (errno)
9602 SIPE_DEBUG_INFO("spawnl returned (%s)!", strerror(errno));
9605 #endif
9607 g_free(mailto);
9609 else
9611 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9616 * A menu which appear when right-clicking on buddy in contact list.
9618 static GList *
9619 sipe_buddy_menu(PurpleBuddy *buddy)
9621 PurpleBlistNode *g_node;
9622 PurpleGroup *group, *gr_parent;
9623 PurpleMenuAction *act;
9624 GList *menu = NULL;
9625 GList *menu_groups = NULL;
9626 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9627 const char *email;
9628 const char *phone;
9629 const char *phone_disp_str;
9630 gchar *self = sip_uri_self(sip);
9632 SIPE_SESSION_FOREACH {
9633 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9635 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9637 PurpleConvChatBuddyFlags flags;
9638 PurpleConvChatBuddyFlags flags_us;
9640 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9641 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9642 if (session->focus_uri
9643 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9644 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9646 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9647 act = purple_menu_action_new(label,
9648 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9649 session->chat_title, NULL);
9650 g_free(label);
9651 menu = g_list_prepend(menu, act);
9654 if (session->focus_uri
9655 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9657 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9658 act = purple_menu_action_new(label,
9659 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9660 session->chat_title, NULL);
9661 g_free(label);
9662 menu = g_list_prepend(menu, act);
9665 else
9667 if (!session->focus_uri
9668 || (session->focus_uri && !session->locked))
9670 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9671 act = purple_menu_action_new(label,
9672 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9673 session->chat_title, NULL);
9674 g_free(label);
9675 menu = g_list_prepend(menu, act);
9679 } SIPE_SESSION_FOREACH_END;
9681 act = purple_menu_action_new(_("New chat"),
9682 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9683 NULL, NULL);
9684 menu = g_list_prepend(menu, act);
9686 if (sip->csta && !sip->csta->line_status) {
9687 gchar *tmp = NULL;
9688 /* work phone */
9689 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9690 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9691 if (phone) {
9692 gchar *label = g_strdup_printf(_("Work %s"),
9693 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9694 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9695 g_free(tmp);
9696 tmp = NULL;
9697 g_free(label);
9698 menu = g_list_prepend(menu, act);
9701 /* mobile phone */
9702 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9703 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9704 if (phone) {
9705 gchar *label = g_strdup_printf(_("Mobile %s"),
9706 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9707 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9708 g_free(tmp);
9709 tmp = NULL;
9710 g_free(label);
9711 menu = g_list_prepend(menu, act);
9714 /* home phone */
9715 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9716 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9717 if (phone) {
9718 gchar *label = g_strdup_printf(_("Home %s"),
9719 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9720 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9721 g_free(tmp);
9722 tmp = NULL;
9723 g_free(label);
9724 menu = g_list_prepend(menu, act);
9727 /* other phone */
9728 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9729 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9730 if (phone) {
9731 gchar *label = g_strdup_printf(_("Other %s"),
9732 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9733 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9734 g_free(tmp);
9735 tmp = NULL;
9736 g_free(label);
9737 menu = g_list_prepend(menu, act);
9740 /* custom1 phone */
9741 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9742 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9743 if (phone) {
9744 gchar *label = g_strdup_printf(_("Custom1 %s"),
9745 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9746 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9747 g_free(tmp);
9748 tmp = NULL;
9749 g_free(label);
9750 menu = g_list_prepend(menu, act);
9754 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9755 if (email) {
9756 act = purple_menu_action_new(_("Send email..."),
9757 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9758 NULL, NULL);
9759 menu = g_list_prepend(menu, act);
9762 gr_parent = purple_buddy_get_group(buddy);
9763 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9764 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9765 continue;
9767 group = (PurpleGroup *)g_node;
9768 if (group == gr_parent)
9769 continue;
9771 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9772 continue;
9774 act = purple_menu_action_new(purple_group_get_name(group),
9775 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9776 group->name, NULL);
9777 menu_groups = g_list_prepend(menu_groups, act);
9779 menu_groups = g_list_reverse(menu_groups);
9781 act = purple_menu_action_new(_("Copy to"),
9782 NULL,
9783 NULL, menu_groups);
9784 menu = g_list_prepend(menu, act);
9785 menu = g_list_reverse(menu);
9787 g_free(self);
9788 return menu;
9791 static void
9792 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9794 struct sipe_account_data *sip = chat->account->gc->proto_data;
9795 struct sip_session *session;
9797 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9798 sipe_conf_modify_conference_lock(sip, session, locked);
9801 static void
9802 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9804 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
9805 sipe_conf_modify_lock(chat, FALSE);
9808 static void
9809 sipe_chat_menu_lock_cb(PurpleChat *chat)
9811 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
9812 sipe_conf_modify_lock(chat, TRUE);
9815 static GList *
9816 sipe_chat_menu(PurpleChat *chat)
9818 PurpleMenuAction *act;
9819 PurpleConvChatBuddyFlags flags_us;
9820 GList *menu = NULL;
9821 struct sipe_account_data *sip = chat->account->gc->proto_data;
9822 struct sip_session *session;
9823 gchar *self;
9825 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9826 if (!session) return NULL;
9828 self = sip_uri_self(sip);
9829 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9831 if (session->focus_uri
9832 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9834 if (session->locked) {
9835 act = purple_menu_action_new(_("Unlock"),
9836 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9837 NULL, NULL);
9838 menu = g_list_prepend(menu, act);
9839 } else {
9840 act = purple_menu_action_new(_("Lock"),
9841 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9842 NULL, NULL);
9843 menu = g_list_prepend(menu, act);
9847 menu = g_list_reverse(menu);
9849 g_free(self);
9850 return menu;
9853 static GList *
9854 sipe_blist_node_menu(PurpleBlistNode *node)
9856 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9857 return sipe_buddy_menu((PurpleBuddy *) node);
9858 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9859 return sipe_chat_menu((PurpleChat *)node);
9860 } else {
9861 return NULL;
9865 static gboolean
9866 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9868 char *uri = trans->payload->data;
9870 PurpleNotifyUserInfo *info;
9871 PurpleBuddy *pbuddy = NULL;
9872 struct sipe_buddy *sbuddy;
9873 const char *alias = NULL;
9874 char *device_name = NULL;
9875 char *server_alias = NULL;
9876 char *phone_number = NULL;
9877 char *email = NULL;
9878 const char *site;
9879 char *first_name = NULL;
9880 char *last_name = NULL;
9882 if (!sip) return FALSE;
9884 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
9886 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9887 alias = purple_buddy_get_local_alias(pbuddy);
9889 //will query buddy UA's capabilities and send answer to log
9890 sipe_options_request(sip, uri);
9892 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9893 if (sbuddy) {
9894 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9897 info = purple_notify_user_info_new();
9899 if (msg->response != 200) {
9900 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
9901 } else {
9902 xmlnode *searchResults;
9903 xmlnode *mrow;
9905 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
9906 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9907 if (!searchResults) {
9908 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
9909 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9910 const char *value;
9911 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9912 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9913 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9915 /* For 2007 system we will take this from ContactCard -
9916 * it has cleaner tel: URIs at least
9918 if (!sip->ocs2007) {
9919 char *tel_uri = sip_to_tel_uri(phone_number);
9920 /* trims its parameters, so call first */
9921 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9922 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9923 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9924 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9925 g_free(tel_uri);
9928 if (server_alias && strlen(server_alias) > 0) {
9929 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9931 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9932 purple_notify_user_info_add_pair(info, _("Job title"), value);
9934 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9935 purple_notify_user_info_add_pair(info, _("Office"), value);
9937 if (phone_number && strlen(phone_number) > 0) {
9938 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9940 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9941 purple_notify_user_info_add_pair(info, _("Company"), value);
9943 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9944 purple_notify_user_info_add_pair(info, _("City"), value);
9946 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9947 purple_notify_user_info_add_pair(info, _("State"), value);
9949 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9950 purple_notify_user_info_add_pair(info, _("Country"), value);
9952 if (email && strlen(email) > 0) {
9953 purple_notify_user_info_add_pair(info, _("Email address"), email);
9957 xmlnode_free(searchResults);
9960 purple_notify_user_info_add_section_break(info);
9962 if (is_empty(server_alias)) {
9963 g_free(server_alias);
9964 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9965 if (server_alias) {
9966 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9970 /* present alias if it differs from server alias */
9971 if (alias && !sipe_strequal(alias, server_alias))
9973 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9976 if (is_empty(email)) {
9977 g_free(email);
9978 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9979 if (email) {
9980 purple_notify_user_info_add_pair(info, _("Email address"), email);
9984 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9985 if (site) {
9986 purple_notify_user_info_add_pair(info, _("Site"), site);
9989 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9990 if (first_name && last_name) {
9991 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9993 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9994 g_free(link);
9996 g_free(first_name);
9997 g_free(last_name);
9999 if (device_name) {
10000 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10003 /* show a buddy's user info in a nice dialog box */
10004 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10005 uri, /* buddy's URI */
10006 info, /* body */
10007 NULL, /* callback called when dialog closed */
10008 NULL); /* userdata for callback */
10010 g_free(phone_number);
10011 g_free(server_alias);
10012 g_free(email);
10013 g_free(device_name);
10015 return TRUE;
10019 * AD search first, LDAP based
10021 static void sipe_get_info(PurpleConnection *gc, const char *username)
10023 struct sipe_account_data *sip = gc->proto_data;
10024 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10025 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10026 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10027 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10029 payload->destroy = g_free;
10030 payload->data = g_strdup(username);
10032 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10033 send_soap_request_with_cb(sip, domain_uri, body,
10034 (TransCallback) process_get_info_response, payload);
10035 g_free(domain_uri);
10036 g_free(body);
10037 g_free(row);
10040 PurplePluginProtocolInfo prpl_info =
10042 OPT_PROTO_CHAT_TOPIC,
10043 NULL, /* user_splits */
10044 NULL, /* protocol_options */
10045 NO_BUDDY_ICONS, /* icon_spec */
10046 sipe_list_icon, /* list_icon */
10047 NULL, /* list_emblems */
10048 sipe_status_text, /* status_text */
10049 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10050 sipe_status_types, /* away_states */
10051 sipe_blist_node_menu, /* blist_node_menu */
10052 NULL, /* chat_info */
10053 NULL, /* chat_info_defaults */
10054 sipe_login, /* login */
10055 sipe_close, /* close */
10056 sipe_im_send, /* send_im */
10057 NULL, /* set_info */ // TODO maybe
10058 sipe_send_typing, /* send_typing */
10059 sipe_get_info, /* get_info */
10060 sipe_set_status, /* set_status */
10061 sipe_set_idle, /* set_idle */
10062 NULL, /* change_passwd */
10063 sipe_add_buddy, /* add_buddy */
10064 NULL, /* add_buddies */
10065 sipe_remove_buddy, /* remove_buddy */
10066 NULL, /* remove_buddies */
10067 sipe_add_permit, /* add_permit */
10068 sipe_add_deny, /* add_deny */
10069 sipe_add_deny, /* rem_permit */
10070 sipe_add_permit, /* rem_deny */
10071 dummy_permit_deny, /* set_permit_deny */
10072 NULL, /* join_chat */
10073 NULL, /* reject_chat */
10074 NULL, /* get_chat_name */
10075 sipe_chat_invite, /* chat_invite */
10076 sipe_chat_leave, /* chat_leave */
10077 NULL, /* chat_whisper */
10078 sipe_chat_send, /* chat_send */
10079 sipe_keep_alive, /* keepalive */
10080 NULL, /* register_user */
10081 NULL, /* get_cb_info */ // deprecated
10082 NULL, /* get_cb_away */ // deprecated
10083 sipe_alias_buddy, /* alias_buddy */
10084 sipe_group_buddy, /* group_buddy */
10085 sipe_rename_group, /* rename_group */
10086 NULL, /* buddy_free */
10087 sipe_convo_closed, /* convo_closed */
10088 purple_normalize_nocase, /* normalize */
10089 NULL, /* set_buddy_icon */
10090 sipe_remove_group, /* remove_group */
10091 NULL, /* get_cb_real_name */ // TODO?
10092 NULL, /* set_chat_topic */
10093 NULL, /* find_blist_chat */
10094 NULL, /* roomlist_get_list */
10095 NULL, /* roomlist_cancel */
10096 NULL, /* roomlist_expand_category */
10097 NULL, /* can_receive_file */
10098 sipe_ft_send_file, /* send_file */
10099 sipe_ft_new_xfer, /* new_xfer */
10100 NULL, /* offline_message */
10101 NULL, /* whiteboard_prpl_ops */
10102 sipe_send_raw, /* send_raw */
10103 NULL, /* roomlist_room_serialize */
10104 NULL, /* unregister_user */
10105 NULL, /* send_attention */
10106 NULL, /* get_attention_types */
10107 #if !PURPLE_VERSION_CHECK(2,5,0)
10108 /* Backward compatibility when compiling against 2.4.x API */
10109 (void (*)(void)) /* _purple_reserved4 */
10110 #endif
10111 sizeof(PurplePluginProtocolInfo), /* struct_size */
10112 #if PURPLE_VERSION_CHECK(2,5,0)
10113 sipe_get_account_text_table, /* get_account_text_table */
10114 #if PURPLE_VERSION_CHECK(2,6,0)
10115 NULL, /* initiate_media */
10116 NULL, /* get_media_caps */
10117 #endif
10118 #endif
10122 PurplePluginInfo info = {
10123 PURPLE_PLUGIN_MAGIC,
10124 PURPLE_MAJOR_VERSION,
10125 PURPLE_MINOR_VERSION,
10126 PURPLE_PLUGIN_PROTOCOL, /**< type */
10127 NULL, /**< ui_requirement */
10128 0, /**< flags */
10129 NULL, /**< dependencies */
10130 PURPLE_PRIORITY_DEFAULT, /**< priority */
10131 "prpl-sipe", /**< id */
10132 "Office Communicator", /**< name */
10133 PACKAGE_VERSION, /**< version */
10134 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10135 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10136 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10137 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10138 "Gabriel Burt <gburt@novell.com>, " /**< author */
10139 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10140 "pier11 <pier11@operamail.com>", /**< author */
10141 PACKAGE_URL, /**< homepage */
10142 sipe_plugin_load, /**< load */
10143 sipe_plugin_unload, /**< unload */
10144 sipe_plugin_destroy, /**< destroy */
10145 NULL, /**< ui_info */
10146 &prpl_info, /**< extra_info */
10147 NULL,
10148 sipe_actions,
10149 NULL,
10150 NULL,
10151 NULL,
10152 NULL
10155 void sipe_core_init(void)
10157 srand(time(NULL));
10158 sip_sec_init();
10160 #ifdef ENABLE_NLS
10161 SIPE_DEBUG_INFO("bindtextdomain = %s",
10162 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10163 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10164 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10165 textdomain(PACKAGE_NAME);
10166 #endif
10169 void sipe_core_destroy(void)
10171 sip_sec_destroy();
10175 Local Variables:
10176 mode: c
10177 c-file-style: "bsd"
10178 indent-tabs-mode: t
10179 tab-width: 8
10180 End: