mingw: fix for G_GNUC_NULL_TERMINATED
[siplcs.git] / src / core / sipe.c
blobeba9fa5eabacdf74473aa853b1c85d58245067a3
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #ifdef _WIN32
41 #ifdef _DLL
42 #define _WS2TCPIP_H_
43 #define _WINSOCK2API_
44 #define _LIBC_INTERNAL_
45 #endif /* _DLL */
46 /* for network */
47 #include "libc_interface.h"
48 #else
49 #include <sys/types.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <unistd.h>
60 #include <glib.h>
62 #include "sipe-common.h"
64 #include "account.h"
65 #include "blist.h"
66 #include "connection.h"
67 #include "conversation.h"
68 #include "core.h"
69 #include "cipher.h"
70 #include "circbuffer.h"
71 #include "debug.h"
72 #include "dnsquery.h"
73 #include "dnssrv.h"
74 #include "ft.h"
75 #include "mime.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 "util.h"
84 #include "version.h"
85 #include "xmlnode.h"
87 #include "core-depurple.h" /* Temporary for the core de-purple transition */
89 #include "sipmsg.h"
90 #include "sip-csta.h"
91 #include "sip-sec.h"
92 #include "sipe-cal.h"
93 #include "sipe-chat.h"
94 #include "sipe-conf.h"
95 #include "sipe-dialog.h"
96 #include "sipe-ews.h"
97 #include "sipe-ft.h"
98 #include "sipe-nls.h"
99 #include "sipe-session.h"
100 #include "sipe-sign.h"
101 #include "sipe-utils.h"
102 #include "sipe-xml.h"
103 #include "http-conn.h"
104 #include "uuid.h"
105 #include "sipe.h"
107 /* Backward compatibility when compiling against 2.4.x API */
108 #if !PURPLE_VERSION_CHECK(2,5,0)
109 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
110 #endif
112 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
114 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
115 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
117 /* Keep in sync with sipe_transport_type! */
118 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
119 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
121 /* Status identifiers (see also: sipe_status_types()) */
122 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
123 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
124 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
125 /* PURPLE_STATUS_UNAVAILABLE: */
126 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
127 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
128 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
129 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
130 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
131 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
132 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
133 /* PURPLE_STATUS_AWAY: */
134 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
135 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
136 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
137 /** Reuters status (user settable) */
138 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
139 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
140 /* ??? PURPLE_STATUS_MOBILE */
141 /* ??? PURPLE_STATUS_TUNE */
143 /* Status attributes (see also sipe_status_types() */
144 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
146 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
148 static struct sipe_activity_map_struct
150 sipe_activity type;
151 const char *token;
152 const char *desc;
153 const char *status_id;
155 } const sipe_activity_map[] =
157 /* This has nothing to do with Availability numbers, like 3500 (online).
158 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
160 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
161 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
162 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
163 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
164 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
165 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
166 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
167 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
168 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
169 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
170 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
171 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
172 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
173 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
174 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
176 /** @param x is sipe_activity */
177 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
180 /* Action name templates */
181 #define ACTION_NAME_PRESENCE "<presence><%s>"
183 static sipe_activity
184 sipe_get_activity_by_token(const char *token)
186 int i;
188 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
190 if (sipe_strequal(token, sipe_activity_map[i].token))
191 return sipe_activity_map[i].type;
194 return sipe_activity_map[0].type;
197 static const char *
198 sipe_get_activity_desc_by_token(const char *token)
200 if (!token) return NULL;
202 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
205 /** Allows to send typed messages from chat window again after account reinstantiation. */
206 static void
207 sipe_rejoin_chat(PurpleConversation *conv)
209 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
210 PURPLE_CONV_CHAT(conv)->left)
212 PURPLE_CONV_CHAT(conv)->left = FALSE;
213 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
217 static char *genbranch()
219 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
220 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
221 rand() & 0xFFFF, rand() & 0xFFFF);
225 static char *default_ua = NULL;
226 static const char*
227 sipe_get_useragent(struct sipe_account_data *sip)
229 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
230 if (is_empty(useragent)) {
231 if (!default_ua) {
232 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
233 /* ref: lzodefs.h */
234 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
235 #define SIPE_TARGET_PLATFORM "linux"
236 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
237 #define SIPE_TARGET_PLATFORM "bsd"
238 #elif defined(__APPLE__) || defined(__MACOS__)
239 #define SIPE_TARGET_PLATFORM "macosx"
240 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
241 #define SIPE_TARGET_PLATFORM "aix"
242 #elif defined(__solaris__) || defined(__sun)
243 #define SIPE_TARGET_PLATFORM "sun"
244 #elif defined(_WIN32)
245 #define SIPE_TARGET_PLATFORM "win"
246 #elif defined(__CYGWIN__)
247 #define SIPE_TARGET_PLATFORM "cygwin"
248 #elif defined(__hpux__)
249 #define SIPE_TARGET_PLATFORM "hpux"
250 #elif defined(__sgi__)
251 #define SIPE_TARGET_PLATFORM "irix"
252 #else
253 #define SIPE_TARGET_PLATFORM "unknown"
254 #endif
256 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
257 #define SIPE_TARGET_ARCH "x86_64"
258 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
259 #define SIPE_TARGET_ARCH "i386"
260 #elif defined(__ppc64__)
261 #define SIPE_TARGET_ARCH "ppc64"
262 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
263 #define SIPE_TARGET_ARCH "ppc"
264 #elif defined(__hppa__) || defined(__hppa)
265 #define SIPE_TARGET_ARCH "hppa"
266 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
267 #define SIPE_TARGET_ARCH "mips"
268 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
269 #define SIPE_TARGET_ARCH "s390"
270 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
271 #define SIPE_TARGET_ARCH "sparc"
272 #elif defined(__arm__)
273 #define SIPE_TARGET_ARCH "arm"
274 #else
275 #define SIPE_TARGET_ARCH "other"
276 #endif
278 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
279 purple_core_get_version(),
280 sip->server_version ? sip->server_version : "");
282 useragent = default_ua;
284 return useragent;
287 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
288 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
290 return "sipe";
293 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
295 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
296 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
297 gpointer data);
299 static void sipe_close(PurpleConnection *gc);
301 static void send_presence_status(struct sipe_account_data *sip);
303 static void sendout_pkt(PurpleConnection *gc, const char *buf);
305 static void sipe_keep_alive(PurpleConnection *gc)
307 struct sipe_account_data *sip = gc->proto_data;
308 if (sip->transport == SIPE_TRANSPORT_UDP) {
309 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
310 gchar buf[2] = {0, 0};
311 purple_debug_info("sipe", "sending keep alive\n");
312 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
313 } else {
314 time_t now = time(NULL);
315 if ((sip->keepalive_timeout > 0) &&
316 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
317 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
319 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
320 sendout_pkt(gc, "\r\n\r\n");
321 sip->last_keepalive = now;
326 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
328 struct sip_connection *ret = NULL;
329 GSList *entry = sip->openconns;
330 while (entry) {
331 ret = entry->data;
332 if (ret->fd == fd) return ret;
333 entry = entry->next;
335 return NULL;
338 static void sipe_auth_free(struct sip_auth *auth)
340 g_free(auth->opaque);
341 auth->opaque = NULL;
342 g_free(auth->realm);
343 auth->realm = NULL;
344 g_free(auth->target);
345 auth->target = NULL;
346 auth->version = 0;
347 auth->type = AUTH_TYPE_UNSET;
348 auth->retries = 0;
349 auth->expires = 0;
350 g_free(auth->gssapi_data);
351 auth->gssapi_data = NULL;
352 sip_sec_destroy_context(auth->gssapi_context);
353 auth->gssapi_context = NULL;
356 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
358 struct sip_connection *ret = g_new0(struct sip_connection, 1);
359 ret->fd = fd;
360 sip->openconns = g_slist_append(sip->openconns, ret);
361 return ret;
364 static void connection_remove(struct sipe_account_data *sip, int fd)
366 struct sip_connection *conn = connection_find(sip, fd);
367 if (conn) {
368 sip->openconns = g_slist_remove(sip->openconns, conn);
369 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
370 g_free(conn->inbuf);
371 g_free(conn);
375 static void connection_free_all(struct sipe_account_data *sip)
377 struct sip_connection *ret = NULL;
378 GSList *entry = sip->openconns;
379 while (entry) {
380 ret = entry->data;
381 connection_remove(sip, ret->fd);
382 entry = sip->openconns;
386 static void
387 sipe_make_signature(struct sipe_account_data *sip,
388 struct sipmsg *msg);
390 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
392 gchar noncecount[9];
393 const char *authuser = sip->authuser;
394 gchar *response;
395 gchar *ret;
397 if (!authuser || strlen(authuser) < 1) {
398 authuser = sip->username;
401 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
402 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
403 gchar *version_str;
405 // If we have a signature for the message, include that
406 if (msg->signature) {
407 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);
410 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
411 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
412 gchar *gssapi_data;
413 gchar *opaque;
414 gchar *sign_str = NULL;
416 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
417 &(auth->expires),
418 auth->type,
419 purple_account_get_bool(sip->account, "sso", TRUE),
420 sip->authdomain ? sip->authdomain : "",
421 authuser,
422 sip->password,
423 auth->target,
424 auth->gssapi_data);
425 if (!gssapi_data || !auth->gssapi_context) {
426 sip->gc->wants_to_die = TRUE;
427 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
428 return NULL;
431 if (auth->version > 3) {
432 sipe_make_signature(sip, msg);
433 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
434 msg->rand, msg->num, msg->signature);
435 } else {
436 sign_str = g_strdup("");
439 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
440 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
441 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);
442 g_free(opaque);
443 g_free(gssapi_data);
444 g_free(version_str);
445 g_free(sign_str);
446 return ret;
449 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
450 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
451 g_free(version_str);
452 return ret;
454 } else { /* Digest */
456 /* Calculate new session key */
457 if (!auth->opaque) {
458 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
459 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
460 authuser, auth->realm, sip->password,
461 auth->gssapi_data, NULL);
464 sprintf(noncecount, "%08d", auth->nc++);
465 response = purple_cipher_http_digest_calculate_response("md5",
466 msg->method, msg->target, NULL, NULL,
467 auth->gssapi_data, noncecount, NULL,
468 auth->opaque);
469 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
471 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);
472 g_free(response);
473 return ret;
477 static char *parse_attribute(const char *attrname, const char *source)
479 const char *tmp, *tmp2;
480 char *retval = NULL;
481 int len = strlen(attrname);
483 if (g_str_has_prefix(source, attrname)) {
484 tmp = source + len;
485 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
486 if (tmp2)
487 retval = g_strndup(tmp, tmp2 - tmp);
488 else
489 retval = g_strdup(tmp);
492 return retval;
495 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
497 int i;
498 gchar **parts;
500 if (!hdr) {
501 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
502 return;
505 if (!g_strncasecmp(hdr, "NTLM", 4)) {
506 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
507 auth->type = AUTH_TYPE_NTLM;
508 hdr += 5;
509 auth->nc = 1;
510 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
511 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
512 auth->type = AUTH_TYPE_KERBEROS;
513 hdr += 9;
514 auth->nc = 3;
515 } else {
516 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
517 auth->type = AUTH_TYPE_DIGEST;
518 hdr += 7;
521 parts = g_strsplit(hdr, "\", ", 0);
522 for (i = 0; parts[i]; i++) {
523 char *tmp;
525 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
527 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
528 g_free(auth->gssapi_data);
529 auth->gssapi_data = tmp;
531 if (auth->type == AUTH_TYPE_NTLM) {
532 /* NTLM module extracts nonce from gssapi-data */
533 auth->nc = 3;
536 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
537 /* Only used with AUTH_TYPE_DIGEST */
538 g_free(auth->gssapi_data);
539 auth->gssapi_data = tmp;
540 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
541 g_free(auth->opaque);
542 auth->opaque = tmp;
543 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
544 g_free(auth->realm);
545 auth->realm = tmp;
547 if (auth->type == AUTH_TYPE_DIGEST) {
548 /* Throw away old session key */
549 g_free(auth->opaque);
550 auth->opaque = NULL;
551 auth->nc = 1;
553 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
554 g_free(auth->target);
555 auth->target = tmp;
556 } else if ((tmp = parse_attribute("version=", parts[i]))) {
557 auth->version = atoi(tmp);
558 g_free(tmp);
560 // uncomment to revert to previous functionality if version 3+ does not work.
561 // auth->version = 2;
563 g_strfreev(parts);
565 return;
568 static void sipe_canwrite_cb(gpointer data,
569 SIPE_UNUSED_PARAMETER gint source,
570 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
572 PurpleConnection *gc = data;
573 struct sipe_account_data *sip = gc->proto_data;
574 gsize max_write;
575 gssize written;
577 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
579 if (max_write == 0) {
580 if (sip->tx_handler != 0){
581 purple_input_remove(sip->tx_handler);
582 sip->tx_handler = 0;
584 return;
587 written = write(sip->fd, sip->txbuf->outptr, max_write);
589 if (written < 0 && errno == EAGAIN)
590 written = 0;
591 else if (written <= 0) {
592 /*TODO: do we really want to disconnect on a failure to write?*/
593 purple_connection_error(gc, _("Could not write"));
594 return;
597 purple_circ_buffer_mark_read(sip->txbuf, written);
600 static void sipe_canwrite_cb_ssl(gpointer data,
601 SIPE_UNUSED_PARAMETER gint src,
602 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
604 PurpleConnection *gc = data;
605 struct sipe_account_data *sip = gc->proto_data;
606 gsize max_write;
607 gssize written;
609 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
611 if (max_write == 0) {
612 if (sip->tx_handler != 0) {
613 purple_input_remove(sip->tx_handler);
614 sip->tx_handler = 0;
615 return;
619 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
621 if (written < 0 && errno == EAGAIN)
622 written = 0;
623 else if (written <= 0) {
624 /*TODO: do we really want to disconnect on a failure to write?*/
625 purple_connection_error(gc, _("Could not write"));
626 return;
629 purple_circ_buffer_mark_read(sip->txbuf, written);
632 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
634 static void send_later_cb(gpointer data, gint source,
635 SIPE_UNUSED_PARAMETER const gchar *error)
637 PurpleConnection *gc = data;
638 struct sipe_account_data *sip;
639 struct sip_connection *conn;
641 if (!PURPLE_CONNECTION_IS_VALID(gc))
643 if (source >= 0)
644 close(source);
645 return;
648 if (source < 0) {
649 purple_connection_error(gc, _("Could not connect"));
650 return;
653 sip = gc->proto_data;
654 sip->fd = source;
655 sip->connecting = FALSE;
656 sip->last_keepalive = time(NULL);
658 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
660 /* If there is more to write now, we need to register a handler */
661 if (sip->txbuf->bufused > 0)
662 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
664 conn = connection_create(sip, source);
665 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
668 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
670 struct sipe_account_data *sip;
672 if (!PURPLE_CONNECTION_IS_VALID(gc))
674 if (gsc) purple_ssl_close(gsc);
675 return NULL;
678 sip = gc->proto_data;
679 sip->fd = gsc->fd;
680 sip->gsc = gsc;
681 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
682 sip->connecting = FALSE;
683 sip->last_keepalive = time(NULL);
685 connection_create(sip, gsc->fd);
687 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
689 return sip;
692 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
693 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
695 PurpleConnection *gc = data;
696 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
697 if (sip == NULL) return;
699 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
701 /* If there is more to write now */
702 if (sip->txbuf->bufused > 0) {
703 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
708 static void sendlater(PurpleConnection *gc, const char *buf)
710 struct sipe_account_data *sip = gc->proto_data;
712 if (!sip->connecting) {
713 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
714 if (sip->transport == SIPE_TRANSPORT_TLS){
715 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
716 } else {
717 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
718 purple_connection_error(gc, _("Could not create socket"));
721 sip->connecting = TRUE;
724 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
725 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
727 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
730 static void sendout_pkt(PurpleConnection *gc, const char *buf)
732 struct sipe_account_data *sip = gc->proto_data;
733 time_t currtime = time(NULL);
734 int writelen = strlen(buf);
735 char *tmp;
737 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
738 g_free(tmp);
739 if (sip->transport == SIPE_TRANSPORT_UDP) {
740 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
741 purple_debug_info("sipe", "could not send packet\n");
743 } else {
744 int ret;
745 if (sip->fd < 0) {
746 sendlater(gc, buf);
747 return;
750 if (sip->tx_handler) {
751 ret = -1;
752 errno = EAGAIN;
753 } else{
754 if (sip->gsc){
755 ret = purple_ssl_write(sip->gsc, buf, writelen);
756 }else{
757 ret = write(sip->fd, buf, writelen);
761 if (ret < 0 && errno == EAGAIN)
762 ret = 0;
763 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
764 sendlater(gc, buf);
765 return;
768 if (ret < writelen) {
769 if (!sip->tx_handler){
770 if (sip->gsc){
771 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
773 else{
774 sip->tx_handler = purple_input_add(sip->fd,
775 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
776 gc);
780 /* XXX: is it OK to do this? You might get part of a request sent
781 with part of another. */
782 if (sip->txbuf->bufused > 0)
783 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
785 purple_circ_buffer_append(sip->txbuf, buf + ret,
786 writelen - ret);
791 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
793 sendout_pkt(gc, buf);
794 return len;
797 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
799 GSList *tmp = msg->headers;
800 gchar *name;
801 gchar *value;
802 GString *outstr = g_string_new("");
803 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
804 while (tmp) {
805 name = ((struct sipnameval*) (tmp->data))->name;
806 value = ((struct sipnameval*) (tmp->data))->value;
807 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
808 tmp = g_slist_next(tmp);
810 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
811 sendout_pkt(sip->gc, outstr->str);
812 g_string_free(outstr, TRUE);
815 static void
816 sipe_make_signature(struct sipe_account_data *sip,
817 struct sipmsg *msg)
819 if (sip->registrar.gssapi_context) {
820 struct sipmsg_breakdown msgbd;
821 gchar *signature_input_str;
822 msgbd.msg = msg;
823 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
824 msgbd.rand = g_strdup_printf("%08x", g_random_int());
825 sip->registrar.ntlm_num++;
826 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
827 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
828 if (signature_input_str != NULL) {
829 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
830 msg->signature = signature_hex;
831 msg->rand = g_strdup(msgbd.rand);
832 msg->num = g_strdup(msgbd.num);
833 g_free(signature_input_str);
835 sipmsg_breakdown_free(&msgbd);
839 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
841 gchar * buf;
843 if (sip->registrar.type == AUTH_TYPE_UNSET) {
844 return;
847 sipe_make_signature(sip, msg);
849 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
850 buf = auth_header(sip, &sip->registrar, msg);
851 if (buf) {
852 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
854 g_free(buf);
855 } 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")) {
856 sip->registrar.nc = 3;
857 sip->registrar.type = AUTH_TYPE_NTLM;
858 #ifdef HAVE_KERBEROS
859 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
860 sip->registrar.type = AUTH_TYPE_KERBEROS;
862 #endif
865 buf = auth_header(sip, &sip->registrar, msg);
866 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
867 g_free(buf);
868 } else {
869 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
873 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
874 const char *text, const char *body)
876 gchar *name;
877 gchar *value;
878 GString *outstr = g_string_new("");
879 struct sipe_account_data *sip = gc->proto_data;
880 gchar *contact;
881 GSList *tmp;
882 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
884 /* Can return NULL! */
885 contact = get_contact(sip);
886 if (contact) {
887 sipmsg_add_header(msg, "Contact", contact);
888 g_free(contact);
891 if (body) {
892 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
893 sipmsg_add_header(msg, "Content-Length", len);
894 g_free(len);
895 } else {
896 sipmsg_add_header(msg, "Content-Length", "0");
899 msg->response = code;
901 sipmsg_strip_headers(msg, keepers);
902 sipmsg_merge_new_headers(msg);
903 sign_outgoing_message(msg, sip, msg->method);
905 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
906 tmp = msg->headers;
907 while (tmp) {
908 name = ((struct sipnameval*) (tmp->data))->name;
909 value = ((struct sipnameval*) (tmp->data))->value;
911 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
912 tmp = g_slist_next(tmp);
914 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
915 sendout_pkt(gc, outstr->str);
916 g_string_free(outstr, TRUE);
919 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
921 if (sip->transactions) {
922 sip->transactions = g_slist_remove(sip->transactions, trans);
923 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
925 if (trans->msg) sipmsg_free(trans->msg);
926 if (trans->payload) {
927 (*trans->payload->destroy)(trans->payload->data);
928 g_free(trans->payload);
930 g_free(trans->key);
931 g_free(trans);
935 static struct transaction *
936 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
938 const gchar *call_id;
939 const gchar *cseq;
940 struct transaction *trans = g_new0(struct transaction, 1);
942 trans->time = time(NULL);
943 trans->msg = (struct sipmsg *)msg;
944 call_id = sipmsg_find_header(trans->msg, "Call-ID");
945 cseq = sipmsg_find_header(trans->msg, "CSeq");
946 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
947 trans->callback = callback;
948 sip->transactions = g_slist_append(sip->transactions, trans);
949 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
950 return trans;
953 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
955 struct transaction *trans;
956 GSList *transactions = sip->transactions;
957 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
958 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
959 gchar *key;
961 if (!call_id || !cseq) {
962 purple_debug(PURPLE_DEBUG_ERROR, "sipe", "transaction_find: no Call-ID or CSeq!\n");
963 return NULL;
966 key = g_strdup_printf("<%s><%s>", call_id, cseq);
967 while (transactions) {
968 trans = transactions->data;
969 if (!g_strcasecmp(trans->key, key)) {
970 g_free(key);
971 return trans;
973 transactions = transactions->next;
976 g_free(key);
977 return NULL;
980 struct transaction *
981 send_sip_request(PurpleConnection *gc, const gchar *method,
982 const gchar *url, const gchar *to, const gchar *addheaders,
983 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
985 struct sipe_account_data *sip = gc->proto_data;
986 const char *addh = "";
987 char *buf;
988 struct sipmsg *msg;
989 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
990 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
991 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
992 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
993 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
994 gchar *route = g_strdup("");
995 gchar *epid = get_epid(sip);
996 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
997 struct transaction *trans = NULL;
999 if (dialog && dialog->routes)
1001 GSList *iter = dialog->routes;
1003 while(iter)
1005 char *tmp = route;
1006 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1007 g_free(tmp);
1008 iter = g_slist_next(iter);
1012 if (!ourtag && !dialog) {
1013 ourtag = gentag();
1016 if (sipe_strequal(method, "REGISTER")) {
1017 if (sip->regcallid) {
1018 g_free(callid);
1019 callid = g_strdup(sip->regcallid);
1020 } else {
1021 sip->regcallid = g_strdup(callid);
1023 cseq = ++sip->cseq;
1026 if (addheaders) addh = addheaders;
1028 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1029 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1030 "From: <sip:%s>%s%s;epid=%s\r\n"
1031 "To: <%s>%s%s%s%s\r\n"
1032 "Max-Forwards: 70\r\n"
1033 "CSeq: %d %s\r\n"
1034 "User-Agent: %s\r\n"
1035 "Call-ID: %s\r\n"
1036 "%s%s"
1037 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1038 method,
1039 dialog && dialog->request ? dialog->request : url,
1040 TRANSPORT_DESCRIPTOR,
1041 purple_network_get_my_ip(-1),
1042 sip->listenport,
1043 branch ? ";branch=" : "",
1044 branch ? branch : "",
1045 sip->username,
1046 ourtag ? ";tag=" : "",
1047 ourtag ? ourtag : "",
1048 epid,
1050 theirtag ? ";tag=" : "",
1051 theirtag ? theirtag : "",
1052 theirepid ? ";epid=" : "",
1053 theirepid ? theirepid : "",
1054 cseq,
1055 method,
1056 sipe_get_useragent(sip),
1057 callid,
1058 route,
1059 addh,
1060 body ? (gsize) strlen(body) : 0,
1061 body ? body : "");
1064 //printf ("parsing msg buf:\n%s\n\n", buf);
1065 msg = sipmsg_parse_msg(buf);
1067 g_free(buf);
1068 g_free(ourtag);
1069 g_free(theirtag);
1070 g_free(theirepid);
1071 g_free(branch);
1072 g_free(callid);
1073 g_free(route);
1074 g_free(epid);
1076 sign_outgoing_message (msg, sip, method);
1078 buf = sipmsg_to_string (msg);
1080 /* add to ongoing transactions */
1081 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1082 if (!sipe_strequal(method, "ACK")) {
1083 trans = transactions_add_buf(sip, msg, tc);
1084 } else {
1085 sipmsg_free(msg);
1087 sendout_pkt(gc, buf);
1088 g_free(buf);
1090 return trans;
1094 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1096 static void
1097 send_soap_request_with_cb(struct sipe_account_data *sip,
1098 gchar *from0,
1099 gchar *body,
1100 TransCallback callback,
1101 struct transaction_payload *payload)
1103 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1104 gchar *contact = get_contact(sip);
1105 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1106 "Content-Type: application/SOAP+xml\r\n",contact);
1108 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1109 trans->payload = payload;
1111 g_free(from);
1112 g_free(contact);
1113 g_free(hdr);
1116 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1118 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1121 static char *get_contact_register(struct sipe_account_data *sip)
1123 char *epid = get_epid(sip);
1124 char *uuid = generateUUIDfromEPID(epid);
1125 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);
1126 g_free(uuid);
1127 g_free(epid);
1128 return(buf);
1131 static void do_register_exp(struct sipe_account_data *sip, int expire)
1133 char *uri;
1134 char *expires;
1135 char *to;
1136 char *contact;
1137 char *hdr;
1139 if (!sip->sipdomain) return;
1141 uri = sip_uri_from_name(sip->sipdomain);
1142 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1143 to = sip_uri_self(sip);
1144 contact = get_contact_register(sip);
1145 hdr = g_strdup_printf("Contact: %s\r\n"
1146 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1147 "Event: registration\r\n"
1148 "Allow-Events: presence\r\n"
1149 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1150 "%s", contact, expires);
1151 g_free(contact);
1152 g_free(expires);
1154 sip->registerstatus = 1;
1156 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1157 process_register_response);
1159 g_free(hdr);
1160 g_free(uri);
1161 g_free(to);
1164 static void do_register_cb(struct sipe_account_data *sip,
1165 SIPE_UNUSED_PARAMETER void *unused)
1167 do_register_exp(sip, -1);
1168 sip->reregister_set = FALSE;
1171 static void do_register(struct sipe_account_data *sip)
1173 do_register_exp(sip, -1);
1176 static void
1177 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1179 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1180 send_soap_request(sip, body);
1181 g_free(body);
1184 static void
1185 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1187 if (allow) {
1188 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1189 } else {
1190 purple_debug_info("sipe", "Blocking contact %s\n", who);
1193 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1196 static
1197 void sipe_auth_user_cb(void * data)
1199 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1200 if (!job) return;
1202 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1203 g_free(job);
1206 static
1207 void sipe_deny_user_cb(void * data)
1209 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1210 if (!job) return;
1212 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1213 g_free(job);
1216 static void
1217 sipe_add_permit(PurpleConnection *gc, const char *name)
1219 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1220 sipe_contact_allow_deny(sip, name, TRUE);
1223 static void
1224 sipe_add_deny(PurpleConnection *gc, const char *name)
1226 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1227 sipe_contact_allow_deny(sip, name, FALSE);
1230 /*static void
1231 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1233 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1234 sipe_contact_set_acl(sip, name, "");
1237 static void
1238 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1240 xmlnode *watchers;
1241 xmlnode *watcher;
1242 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1243 if (msg->response != 0 && msg->response != 200) return;
1245 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1247 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1248 if (!watchers) return;
1250 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1251 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1252 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1253 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1255 // TODO pull out optional displayName to pass as alias
1256 if (remote_user) {
1257 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1258 job->who = remote_user;
1259 job->sip = sip;
1260 purple_account_request_authorization(
1261 sip->account,
1262 remote_user,
1263 _("you"), /* id */
1264 alias,
1265 NULL, /* message */
1266 on_list,
1267 sipe_auth_user_cb,
1268 sipe_deny_user_cb,
1269 (void *) job);
1274 xmlnode_free(watchers);
1275 return;
1278 static void
1279 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1281 PurpleGroup * purple_group = purple_find_group(group->name);
1282 if (!purple_group) {
1283 purple_group = purple_group_new(group->name);
1284 purple_blist_add_group(purple_group, NULL);
1287 if (purple_group) {
1288 group->purple_group = purple_group;
1289 sip->groups = g_slist_append(sip->groups, group);
1290 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1291 } else {
1292 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1296 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1298 struct sipe_group *group;
1299 GSList *entry;
1300 if (sip == NULL) {
1301 return NULL;
1304 entry = sip->groups;
1305 while (entry) {
1306 group = entry->data;
1307 if (group->id == id) {
1308 return group;
1310 entry = entry->next;
1312 return NULL;
1315 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1317 struct sipe_group *group;
1318 GSList *entry;
1319 if (!sip || !name) {
1320 return NULL;
1323 entry = sip->groups;
1324 while (entry) {
1325 group = entry->data;
1326 if (sipe_strequal(group->name, name)) {
1327 return group;
1329 entry = entry->next;
1331 return NULL;
1334 static void
1335 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1337 gchar *body;
1338 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1339 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1340 send_soap_request(sip, body);
1341 g_free(body);
1342 g_free(group->name);
1343 group->name = g_strdup(name);
1347 * Only appends if no such value already stored.
1348 * Like Set in Java.
1350 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1351 GSList * res = list;
1352 if (!g_slist_find_custom(list, data, func)) {
1353 res = g_slist_insert_sorted(list, data, func);
1355 return res;
1358 static int
1359 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1360 return group1->id - group2->id;
1364 * Returns string like "2 4 7 8" - group ids buddy belong to.
1366 static gchar *
1367 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1368 int i = 0;
1369 gchar *res;
1370 //creating array from GList, converting int to gchar*
1371 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1372 GSList *entry = buddy->groups;
1374 if (!ids_arr) return NULL;
1376 while (entry) {
1377 struct sipe_group * group = entry->data;
1378 ids_arr[i] = g_strdup_printf("%d", group->id);
1379 entry = entry->next;
1380 i++;
1382 ids_arr[i] = NULL;
1383 res = g_strjoinv(" ", ids_arr);
1384 g_strfreev(ids_arr);
1385 return res;
1389 * Sends buddy update to server
1391 static void
1392 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1394 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1395 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1397 if (buddy && purple_buddy) {
1398 const char *alias = purple_buddy_get_alias(purple_buddy);
1399 gchar *groups = sipe_get_buddy_groups_string(buddy);
1400 if (groups) {
1401 gchar *body;
1402 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1404 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1405 alias, groups, "true", buddy->name, sip->contacts_delta++
1407 send_soap_request(sip, body);
1408 g_free(groups);
1409 g_free(body);
1414 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1416 if (msg->response == 200) {
1417 struct sipe_group *group;
1418 struct group_user_context *ctx = trans->payload->data;
1419 xmlnode *xml;
1420 xmlnode *node;
1421 char *group_id;
1422 struct sipe_buddy *buddy;
1424 xml = xmlnode_from_str(msg->body, msg->bodylen);
1425 if (!xml) {
1426 return FALSE;
1429 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1430 if (!node) {
1431 xmlnode_free(xml);
1432 return FALSE;
1435 group_id = xmlnode_get_data(node);
1436 if (!group_id) {
1437 xmlnode_free(xml);
1438 return FALSE;
1441 group = g_new0(struct sipe_group, 1);
1442 group->id = (int)g_ascii_strtod(group_id, NULL);
1443 g_free(group_id);
1444 group->name = g_strdup(ctx->group_name);
1446 sipe_group_add(sip, group);
1448 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1449 if (buddy) {
1450 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1453 sipe_group_set_user(sip, ctx->user_name);
1455 xmlnode_free(xml);
1456 return TRUE;
1458 return FALSE;
1461 static void sipe_group_context_destroy(gpointer data)
1463 struct group_user_context *ctx = data;
1464 g_free(ctx->group_name);
1465 g_free(ctx->user_name);
1466 g_free(ctx);
1469 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1471 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1472 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1473 gchar *body;
1474 ctx->group_name = g_strdup(name);
1475 ctx->user_name = g_strdup(who);
1476 payload->destroy = sipe_group_context_destroy;
1477 payload->data = ctx;
1479 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1480 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1481 g_free(body);
1485 * Data structure for scheduled actions
1488 struct scheduled_action {
1490 * Name of action.
1491 * Format is <Event>[<Data>...]
1492 * Example: <presence><sip:user@domain.com> or <registration>
1494 gchar *name;
1495 guint timeout_handler;
1496 gboolean repetitive;
1497 Action action;
1498 GDestroyNotify destroy;
1499 struct sipe_account_data *sip;
1500 void *payload;
1504 * A timer callback
1505 * Should return FALSE if repetitive action is not needed
1507 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1509 gboolean ret;
1510 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1511 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1512 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1513 (sched_action->action)(sched_action->sip, sched_action->payload);
1514 ret = sched_action->repetitive;
1515 if (sched_action->destroy) {
1516 (*sched_action->destroy)(sched_action->payload);
1518 g_free(sched_action->name);
1519 g_free(sched_action);
1520 return ret;
1524 * Kills action timer effectively cancelling
1525 * scheduled action
1527 * @param name of action
1529 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1531 GSList *entry;
1533 if (!sip->timeouts || !name) return;
1535 entry = sip->timeouts;
1536 while (entry) {
1537 struct scheduled_action *sched_action = entry->data;
1538 if(sipe_strequal(sched_action->name, name)) {
1539 GSList *to_delete = entry;
1540 entry = entry->next;
1541 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1542 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1543 purple_timeout_remove(sched_action->timeout_handler);
1544 if (sched_action->destroy) {
1545 (*sched_action->destroy)(sched_action->payload);
1547 g_free(sched_action->name);
1548 g_free(sched_action);
1549 } else {
1550 entry = entry->next;
1555 static void
1556 sipe_schedule_action0(const gchar *name,
1557 int timeout,
1558 gboolean isSeconds,
1559 Action action,
1560 GDestroyNotify destroy,
1561 struct sipe_account_data *sip,
1562 void *payload)
1564 struct scheduled_action *sched_action;
1566 /* Make sure each action only exists once */
1567 sipe_cancel_scheduled_action(sip, name);
1569 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1570 sched_action = g_new0(struct scheduled_action, 1);
1571 sched_action->repetitive = FALSE;
1572 sched_action->name = g_strdup(name);
1573 sched_action->action = action;
1574 sched_action->destroy = destroy;
1575 sched_action->sip = sip;
1576 sched_action->payload = payload;
1577 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1578 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1579 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1580 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1583 void
1584 sipe_schedule_action(const gchar *name,
1585 int timeout,
1586 Action action,
1587 GDestroyNotify destroy,
1588 struct sipe_account_data *sip,
1589 void *payload)
1591 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1595 * Same as sipe_schedule_action() but timeout is in milliseconds.
1597 static void
1598 sipe_schedule_action_msec(const gchar *name,
1599 int timeout,
1600 Action action,
1601 GDestroyNotify destroy,
1602 struct sipe_account_data *sip,
1603 void *payload)
1605 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1608 static void
1609 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1610 time_t calculate_from);
1612 static int
1613 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1615 static const char*
1616 sipe_get_status_by_availability(int avail,
1617 char** activity);
1619 static void
1620 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1621 const char *status_id,
1622 const char *message,
1623 time_t do_not_publish[]);
1625 static void
1626 sipe_apply_calendar_status(struct sipe_account_data *sip,
1627 struct sipe_buddy *sbuddy,
1628 const char *status_id)
1630 time_t cal_avail_since;
1631 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1632 int avail;
1633 gchar *self_uri;
1635 if (!sbuddy) return;
1637 if (cal_status < SIPE_CAL_NO_DATA) {
1638 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_status : %d for %s\n", cal_status, sbuddy->name);
1639 purple_debug_info("sipe", "sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1642 /* scheduled Cal update call */
1643 if (!status_id) {
1644 status_id = sbuddy->last_non_cal_status_id;
1645 g_free(sbuddy->activity);
1646 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1649 if (!status_id) {
1650 purple_debug_info("sipe", "sipe_apply_calendar_status: status_id is NULL for %s, exiting.\n",
1651 sbuddy->name ? sbuddy->name : "" );
1652 return;
1655 /* adjust to calendar status */
1656 if (cal_status != SIPE_CAL_NO_DATA) {
1657 purple_debug_info("sipe", "sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1659 if (cal_status == SIPE_CAL_BUSY
1660 && cal_avail_since > sbuddy->user_avail_since
1661 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1663 status_id = SIPE_STATUS_ID_BUSY;
1664 g_free(sbuddy->activity);
1665 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1667 avail = sipe_get_availability_by_status(status_id, NULL);
1669 purple_debug_info("sipe", "sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1670 if (cal_avail_since > sbuddy->activity_since) {
1671 if (cal_status == SIPE_CAL_OOF
1672 && avail >= 15000) /* 12000 in 2007 */
1674 g_free(sbuddy->activity);
1675 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1680 /* then set status_id actually */
1681 purple_debug_info("sipe", "sipe_apply_calendar_status: to %s for %s\n", status_id, sbuddy->name ? sbuddy->name : "" );
1682 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1684 /* set our account state to the one in roaming (including calendar info) */
1685 self_uri = sip_uri_self(sip);
1686 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1687 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1688 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1691 purple_debug_info("sipe", "sipe_apply_calendar_status: switch to '%s' for the account\n", sip->status);
1692 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1694 g_free(self_uri);
1697 static void
1698 sipe_got_user_status(struct sipe_account_data *sip,
1699 const char* uri,
1700 const char *status_id)
1702 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1704 if (!sbuddy) return;
1706 /* Check if on 2005 system contact's calendar,
1707 * then set/preserve it.
1709 if (!sip->ocs2007) {
1710 sipe_apply_calendar_status(sip, sbuddy, status_id);
1711 } else {
1712 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1716 static void
1717 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1718 struct sipe_buddy *sbuddy,
1719 struct sipe_account_data *sip)
1721 sipe_apply_calendar_status(sip, sbuddy, NULL);
1725 * Updates contact's status
1726 * based on their calendar information.
1728 * Applicability: 2005 systems
1730 static void
1731 update_calendar_status(struct sipe_account_data *sip)
1733 purple_debug_info("sipe", "update_calendar_status() started.\n");
1734 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1736 /* repeat scheduling */
1737 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1741 * Schedules process of contacts' status update
1742 * based on their calendar information.
1743 * Should be scheduled to the beginning of every
1744 * 15 min interval, like:
1745 * 13:00, 13:15, 13:30, 13:45, etc.
1747 * Applicability: 2005 systems
1749 static void
1750 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1751 time_t calculate_from)
1753 int interval = 15*60;
1754 /** start of the beginning of closest 15 min interval. */
1755 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1757 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1758 asctime(localtime(&calculate_from)));
1759 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1760 asctime(localtime(&next_start)));
1762 sipe_schedule_action("<+2005-cal-status>",
1763 (int)(next_start - time(NULL)),
1764 (Action)update_calendar_status,
1765 NULL,
1766 sip,
1767 NULL);
1771 * Schedules process of self status publish
1772 * based on own calendar information.
1773 * Should be scheduled to the beginning of every
1774 * 15 min interval, like:
1775 * 13:00, 13:15, 13:30, 13:45, etc.
1777 * Applicability: 2007+ systems
1779 static void
1780 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1781 time_t calculate_from)
1783 int interval = 5*60;
1784 /** start of the beginning of closest 5 min interval. */
1785 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1787 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1788 asctime(localtime(&calculate_from)));
1789 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1790 asctime(localtime(&next_start)));
1792 sipe_schedule_action("<+2007-cal-status>",
1793 (int)(next_start - time(NULL)),
1794 (Action)publish_calendar_status_self,
1795 NULL,
1796 sip,
1797 NULL);
1800 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1802 /** Should be g_free()'d
1804 static gchar *
1805 sipe_get_subscription_key(const gchar *event,
1806 const gchar *with)
1808 gchar *key = NULL;
1810 if (is_empty(event)) return NULL;
1812 if (event && sipe_strcase_equal(event, "presence")) {
1813 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1814 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1816 /* @TODO drop participated buddies' just_added flag */
1817 } else if (event) {
1818 /* Subscription is identified by <event> key */
1819 key = g_strdup_printf("<%s>", event);
1822 return key;
1825 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1826 SIPE_UNUSED_PARAMETER struct transaction *trans)
1828 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1829 const gchar *event = sipmsg_find_header(msg, "Event");
1830 gchar *key;
1832 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1833 if (!event) {
1834 struct sipmsg *request_msg = trans->msg;
1835 event = sipmsg_find_header(request_msg, "Event");
1838 key = sipe_get_subscription_key(event, with);
1840 /* 200 OK; 481 Call Leg Does Not Exist */
1841 if (key && (msg->response == 200 || msg->response == 481)) {
1842 if (g_hash_table_lookup(sip->subscriptions, key)) {
1843 g_hash_table_remove(sip->subscriptions, key);
1844 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1848 /* create/store subscription dialog if not yet */
1849 if (msg->response == 200) {
1850 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1851 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1853 if (key) {
1854 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1855 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1857 subscription->dialog.callid = g_strdup(callid);
1858 subscription->dialog.cseq = atoi(cseq);
1859 subscription->dialog.with = g_strdup(with);
1860 subscription->event = g_strdup(event);
1861 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1863 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1866 g_free(cseq);
1869 g_free(key);
1870 g_free(with);
1872 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1874 process_incoming_notify(sip, msg, FALSE, FALSE);
1876 return TRUE;
1879 static void sipe_subscribe_resource_uri(const char *name,
1880 SIPE_UNUSED_PARAMETER gpointer value,
1881 gchar **resources_uri)
1883 gchar *tmp = *resources_uri;
1884 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1885 g_free(tmp);
1888 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1890 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1891 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1892 gchar *tmp = *resources_uri;
1894 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1896 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1897 g_free(tmp);
1901 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1902 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1903 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1904 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1905 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1908 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1910 gchar *key;
1911 gchar *contact = get_contact(sip);
1912 gchar *request;
1913 gchar *content;
1914 gchar *require = "";
1915 gchar *accept = "";
1916 gchar *autoextend = "";
1917 gchar *content_type;
1918 struct sip_dialog *dialog;
1920 if (sip->ocs2007) {
1921 require = ", categoryList";
1922 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1923 content_type = "application/msrtc-adrl-categorylist+xml";
1924 content = g_strdup_printf(
1925 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1926 "<action name=\"subscribe\" id=\"63792024\">\n"
1927 "<adhocList>\n%s</adhocList>\n"
1928 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1929 "<category name=\"calendarData\"/>\n"
1930 "<category name=\"contactCard\"/>\n"
1931 "<category name=\"note\"/>\n"
1932 "<category name=\"state\"/>\n"
1933 "</categoryList>\n"
1934 "</action>\n"
1935 "</batchSub>", sip->username, resources_uri);
1936 } else {
1937 autoextend = "Supported: com.microsoft.autoextend\r\n";
1938 content_type = "application/adrl+xml";
1939 content = g_strdup_printf(
1940 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1941 "<create xmlns=\"\">\n%s</create>\n"
1942 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1944 g_free(resources_uri);
1946 request = g_strdup_printf(
1947 "Require: adhoclist%s\r\n"
1948 "Supported: eventlist\r\n"
1949 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1950 "Supported: ms-piggyback-first-notify\r\n"
1951 "%sSupported: ms-benotify\r\n"
1952 "Proxy-Require: ms-benotify\r\n"
1953 "Event: presence\r\n"
1954 "Content-Type: %s\r\n"
1955 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1956 g_free(contact);
1958 /* subscribe to buddy presence */
1959 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1960 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1961 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1962 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1964 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1966 g_free(content);
1967 g_free(to);
1968 g_free(request);
1969 g_free(key);
1972 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1973 SIPE_UNUSED_PARAMETER void *unused)
1975 gchar *to = sip_uri_self(sip);
1976 gchar *resources_uri = g_strdup("");
1977 if (sip->ocs2007) {
1978 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1979 } else {
1980 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1983 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1986 struct presence_batched_routed {
1987 gchar *host;
1988 GSList *buddies;
1991 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1993 struct presence_batched_routed *data = payload;
1994 GSList *buddies = data->buddies;
1995 while (buddies) {
1996 g_free(buddies->data);
1997 buddies = buddies->next;
1999 g_slist_free(data->buddies);
2000 g_free(data->host);
2001 g_free(payload);
2004 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2006 struct presence_batched_routed *data = payload;
2007 GSList *buddies = data->buddies;
2008 gchar *resources_uri = g_strdup("");
2009 while (buddies) {
2010 gchar *tmp = resources_uri;
2011 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2012 g_free(tmp);
2013 buddies = buddies->next;
2015 sipe_subscribe_presence_batched_to(sip, resources_uri,
2016 g_strdup(data->host));
2020 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2021 * The user sends a single SUBSCRIBE request to the subscribed contact.
2022 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2026 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2029 gchar *key;
2030 gchar *to = sip_uri((char *)buddy_name);
2031 gchar *tmp = get_contact(sip);
2032 gchar *request;
2033 gchar *content = NULL;
2034 gchar *autoextend = "";
2035 gchar *content_type = "";
2036 struct sip_dialog *dialog;
2037 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2038 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2040 if (sbuddy) sbuddy->just_added = FALSE;
2042 if (sip->ocs2007) {
2043 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2044 } else {
2045 autoextend = "Supported: com.microsoft.autoextend\r\n";
2048 request = g_strdup_printf(
2049 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2050 "Supported: ms-piggyback-first-notify\r\n"
2051 "%s%sSupported: ms-benotify\r\n"
2052 "Proxy-Require: ms-benotify\r\n"
2053 "Event: presence\r\n"
2054 "Contact: %s\r\n", autoextend, content_type, tmp);
2056 if (sip->ocs2007) {
2057 content = g_strdup_printf(
2058 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2059 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2060 "<resource uri=\"%s\"%s\n"
2061 "</adhocList>\n"
2062 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2063 "<category name=\"calendarData\"/>\n"
2064 "<category name=\"contactCard\"/>\n"
2065 "<category name=\"note\"/>\n"
2066 "<category name=\"state\"/>\n"
2067 "</categoryList>\n"
2068 "</action>\n"
2069 "</batchSub>", sip->username, to, context);
2072 g_free(tmp);
2074 /* subscribe to buddy presence */
2075 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2076 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2077 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2078 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2080 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2082 g_free(content);
2083 g_free(to);
2084 g_free(request);
2085 g_free(key);
2088 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2090 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
2092 if (!purple_status_is_active(status))
2093 return;
2095 if (account->gc) {
2096 struct sipe_account_data *sip = account->gc->proto_data;
2098 if (sip) {
2099 gchar *action_name;
2100 gchar *tmp;
2101 time_t now = time(NULL);
2102 const char *status_id = purple_status_get_id(status);
2103 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2104 sipe_activity activity = sipe_get_activity_by_token(status_id);
2105 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2107 /* when other point of presence clears note, but we are keeping
2108 * state if OOF note.
2110 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2111 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2112 do_not_publish = FALSE;
2115 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2116 status_id, (int)sip->do_not_publish[activity], (int)now);
2118 sip->do_not_publish[activity] = 0;
2119 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2120 status_id, (int)sip->do_not_publish[activity]);
2122 if (do_not_publish)
2124 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2125 return;
2128 g_free(sip->status);
2129 sip->status = g_strdup(status_id);
2131 /* hack to escape apostrof before comparison */
2132 tmp = note ? purple_strreplace(note, "'", "&apos;") : NULL;
2134 /* this will preserve OOF flag as well */
2135 if (!sipe_strequal(tmp, sip->note)) {
2136 sip->is_oof_note = FALSE;
2137 g_free(sip->note);
2138 sip->note = g_strdup(note);
2139 sip->note_since = time(NULL);
2141 g_free(tmp);
2143 /* schedule 2 sec to capture idle flag */
2144 action_name = g_strdup_printf("<%s>", "+set-status");
2145 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2146 g_free(action_name);
2150 static void
2151 sipe_set_idle(PurpleConnection * gc,
2152 int interval)
2154 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval);
2156 if (gc) {
2157 struct sipe_account_data *sip = gc->proto_data;
2159 if (sip) {
2160 sip->idle_switch = time(NULL);
2161 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2166 static void
2167 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2168 SIPE_UNUSED_PARAMETER const char *alias)
2170 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2171 sipe_group_set_user(sip, name);
2174 static void
2175 sipe_group_buddy(PurpleConnection *gc,
2176 const char *who,
2177 const char *old_group_name,
2178 const char *new_group_name)
2180 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2181 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2182 struct sipe_group * old_group = NULL;
2183 struct sipe_group * new_group;
2185 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2186 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2188 if(!buddy) { // buddy not in roaming list
2189 return;
2192 if (old_group_name) {
2193 old_group = sipe_group_find_by_name(sip, old_group_name);
2195 new_group = sipe_group_find_by_name(sip, new_group_name);
2197 if (old_group) {
2198 buddy->groups = g_slist_remove(buddy->groups, old_group);
2199 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
2202 if (!new_group) {
2203 sipe_group_create(sip, new_group_name, who);
2204 } else {
2205 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2206 sipe_group_set_user(sip, who);
2210 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2212 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2214 /* libpurple can call us with undefined buddy or group */
2215 if (buddy && group) {
2216 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2218 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2219 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2220 purple_blist_rename_buddy(buddy, buddy_name);
2221 g_free(buddy_name);
2223 /* Prepend sip: if needed */
2224 if (!g_str_has_prefix(buddy->name, "sip:")) {
2225 gchar *buf = sip_uri_from_name(buddy->name);
2226 purple_blist_rename_buddy(buddy, buf);
2227 g_free(buf);
2230 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2231 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2232 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
2233 b->name = g_strdup(buddy->name);
2234 b->just_added = TRUE;
2235 g_hash_table_insert(sip->buddies, b->name, b);
2236 sipe_group_buddy(gc, b->name, NULL, group->name);
2237 /* @TODO should go to callback */
2238 sipe_subscribe_presence_single(sip, b->name);
2239 } else {
2240 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
2245 static void sipe_free_buddy(struct sipe_buddy *buddy)
2247 #ifndef _WIN32
2249 * We are calling g_hash_table_foreach_steal(). That means that no
2250 * key/value deallocation functions are called. Therefore the glib
2251 * hash code does not touch the key (buddy->name) or value (buddy)
2252 * of the to-be-deleted hash node at all. It follows that we
2254 * - MUST free the memory for the key ourselves and
2255 * - ARE allowed to do it in this function
2257 * Conclusion: glib must be broken on the Windows platform if sipe
2258 * crashes with SIGTRAP when closing. You'll have to live
2259 * with the memory leak until this is fixed.
2261 g_free(buddy->name);
2262 #endif
2263 g_free(buddy->activity);
2264 g_free(buddy->meeting_subject);
2265 g_free(buddy->meeting_location);
2266 g_free(buddy->note);
2268 g_free(buddy->cal_start_time);
2269 g_free(buddy->cal_free_busy_base64);
2270 g_free(buddy->cal_free_busy);
2271 g_free(buddy->last_non_cal_activity);
2273 sipe_cal_free_working_hours(buddy->cal_working_hours);
2275 g_free(buddy->device_name);
2276 g_slist_free(buddy->groups);
2277 g_free(buddy);
2281 * Unassociates buddy from group first.
2282 * Then see if no groups left, removes buddy completely.
2283 * Otherwise updates buddy groups on server.
2285 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2287 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2288 struct sipe_buddy *b;
2289 struct sipe_group *g = NULL;
2291 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
2292 if (!buddy) return;
2294 b = g_hash_table_lookup(sip->buddies, buddy->name);
2295 if (!b) return;
2297 if (group) {
2298 g = sipe_group_find_by_name(sip, group->name);
2301 if (g) {
2302 b->groups = g_slist_remove(b->groups, g);
2303 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
2306 if (g_slist_length(b->groups) < 1) {
2307 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2308 sipe_cancel_scheduled_action(sip, action_name);
2309 g_free(action_name);
2311 g_hash_table_remove(sip->buddies, buddy->name);
2313 if (b->name) {
2314 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2315 send_soap_request(sip, body);
2316 g_free(body);
2319 sipe_free_buddy(b);
2320 } else {
2321 //updates groups on server
2322 sipe_group_set_user(sip, b->name);
2327 static void
2328 sipe_rename_group(PurpleConnection *gc,
2329 const char *old_name,
2330 PurpleGroup *group,
2331 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2333 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2334 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2335 if (s_group) {
2336 sipe_group_rename(sip, s_group, group->name);
2337 } else {
2338 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
2342 static void
2343 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2345 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2346 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2347 if (s_group) {
2348 gchar *body;
2349 purple_debug_info("sipe", "Deleting group %s\n", group->name);
2350 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2351 send_soap_request(sip, body);
2352 g_free(body);
2354 sip->groups = g_slist_remove(sip->groups, s_group);
2355 g_free(s_group->name);
2356 g_free(s_group);
2357 } else {
2358 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
2362 /** All statuses need message attribute to pass Note */
2363 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2365 PurpleStatusType *type;
2366 GList *types = NULL;
2368 /* Macros to reduce code repetition.
2369 Translators: noun */
2370 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2371 prim, id, name, \
2372 TRUE, user, FALSE, \
2373 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2374 NULL); \
2375 types = g_list_append(types, type);
2377 /* Online */
2378 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2379 NULL,
2380 NULL,
2381 TRUE);
2383 /* Busy */
2384 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2385 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2386 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2387 TRUE);
2389 /* Do Not Disturb */
2390 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2391 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2392 NULL,
2393 TRUE);
2395 /* Away */
2396 /* Goes first in the list as
2397 * purple picks the first status with the AWAY type
2398 * for idle.
2400 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2401 NULL,
2402 NULL,
2403 TRUE);
2405 /* Be Right Back */
2406 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2407 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2408 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2409 TRUE);
2411 /* Appear Offline */
2412 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2413 NULL,
2414 NULL,
2415 TRUE);
2417 /* Offline */
2418 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2419 NULL,
2420 NULL,
2421 TRUE);
2422 types = g_list_append(types, type);
2424 return types;
2428 * A callback for g_hash_table_foreach
2430 static void
2431 sipe_buddy_subscribe_cb(char *buddy_name,
2432 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2433 struct sipe_account_data *sip)
2435 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2436 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2437 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2438 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2440 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2441 g_free(action_name);
2445 * Removes entries from purple buddy list
2446 * that does not correspond ones in the roaming contact list.
2448 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2449 GSList *buddies = purple_find_buddies(sip->account, NULL);
2450 GSList *entry = buddies;
2451 struct sipe_buddy *buddy;
2452 PurpleBuddy *b;
2453 PurpleGroup *g;
2455 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2456 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2457 while (entry) {
2458 b = entry->data;
2459 g = purple_buddy_get_group(b);
2460 buddy = g_hash_table_lookup(sip->buddies, b->name);
2461 if(buddy) {
2462 gboolean in_sipe_groups = FALSE;
2463 GSList *entry2 = buddy->groups;
2464 while (entry2) {
2465 struct sipe_group *group = entry2->data;
2466 if (sipe_strequal(group->name, g->name)) {
2467 in_sipe_groups = TRUE;
2468 break;
2470 entry2 = entry2->next;
2472 if(!in_sipe_groups) {
2473 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2474 purple_blist_remove_buddy(b);
2476 } else {
2477 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2478 purple_blist_remove_buddy(b);
2480 entry = entry->next;
2482 g_slist_free(buddies);
2485 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2487 int len = msg->bodylen;
2489 const gchar *tmp = sipmsg_find_header(msg, "Event");
2490 xmlnode *item;
2491 xmlnode *isc;
2492 const gchar *contacts_delta;
2493 xmlnode *group_node;
2494 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2495 return FALSE;
2498 /* Convert the contact from XML to Purple Buddies */
2499 isc = xmlnode_from_str(msg->body, len);
2500 if (!isc) {
2501 return FALSE;
2504 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2505 if (contacts_delta) {
2506 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2509 if (sipe_strequal(isc->name, "contactList")) {
2511 /* Parse groups */
2512 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2513 struct sipe_group * group = g_new0(struct sipe_group, 1);
2514 const char *name = xmlnode_get_attrib(group_node, "name");
2516 if (g_str_has_prefix(name, "~")) {
2517 name = _("Other Contacts");
2519 group->name = g_strdup(name);
2520 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2522 sipe_group_add(sip, group);
2525 // Make sure we have at least one group
2526 if (g_slist_length(sip->groups) == 0) {
2527 struct sipe_group * group = g_new0(struct sipe_group, 1);
2528 PurpleGroup *purple_group;
2529 group->name = g_strdup(_("Other Contacts"));
2530 group->id = 1;
2531 purple_group = purple_group_new(group->name);
2532 purple_blist_add_group(purple_group, NULL);
2533 sip->groups = g_slist_append(sip->groups, group);
2536 /* Parse contacts */
2537 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2538 const gchar *uri = xmlnode_get_attrib(item, "uri");
2539 const gchar *name = xmlnode_get_attrib(item, "name");
2540 gchar *buddy_name;
2541 struct sipe_buddy *buddy = NULL;
2542 gchar *tmp;
2543 gchar **item_groups;
2544 int i = 0;
2546 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2547 tmp = sip_uri_from_name(uri);
2548 buddy_name = g_ascii_strdown(tmp, -1);
2549 g_free(tmp);
2551 /* assign to group Other Contacts if nothing else received */
2552 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2553 if(is_empty(tmp)) {
2554 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2555 g_free(tmp);
2556 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2558 item_groups = g_strsplit(tmp, " ", 0);
2559 g_free(tmp);
2561 while (item_groups[i]) {
2562 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2564 // If couldn't find the right group for this contact, just put them in the first group we have
2565 if (group == NULL && g_slist_length(sip->groups) > 0) {
2566 group = sip->groups->data;
2569 if (group != NULL) {
2570 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2571 if (!b){
2572 b = purple_buddy_new(sip->account, buddy_name, uri);
2573 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2575 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2578 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2579 if (name != NULL && strlen(name) != 0) {
2580 purple_blist_alias_buddy(b, name);
2582 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2586 if (!buddy) {
2587 buddy = g_new0(struct sipe_buddy, 1);
2588 buddy->name = g_strdup(b->name);
2589 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2592 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2594 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2595 } else {
2596 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2597 name);
2600 i++;
2601 } // while, contact groups
2602 g_strfreev(item_groups);
2603 g_free(buddy_name);
2605 } // for, contacts
2607 sipe_cleanup_local_blist(sip);
2609 /* Add self-contact if not there yet. 2005 systems. */
2610 /* This will resemble subscription to roaming_self in 2007 systems */
2611 if (!sip->ocs2007) {
2612 gchar *self_uri = sip_uri_self(sip);
2613 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2615 if (!buddy) {
2616 buddy = g_new0(struct sipe_buddy, 1);
2617 buddy->name = g_strdup(self_uri);
2618 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2620 g_free(self_uri);
2623 xmlnode_free(isc);
2625 /* subscribe to buddies */
2626 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2627 if (sip->batched_support) {
2628 sipe_subscribe_presence_batched(sip, NULL);
2629 } else {
2630 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2632 sip->subscribed_buddies = TRUE;
2634 /* for 2005 systems schedule contacts' status update
2635 * based on their calendar information
2637 if (!sip->ocs2007) {
2638 sipe_sched_calendar_status_update(sip, time(NULL));
2641 return 0;
2645 * Subscribe roaming contacts
2647 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2649 gchar *to = sip_uri_self(sip);
2650 gchar *tmp = get_contact(sip);
2651 gchar *hdr = g_strdup_printf(
2652 "Event: vnd-microsoft-roaming-contacts\r\n"
2653 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2654 "Supported: com.microsoft.autoextend\r\n"
2655 "Supported: ms-benotify\r\n"
2656 "Proxy-Require: ms-benotify\r\n"
2657 "Supported: ms-piggyback-first-notify\r\n"
2658 "Contact: %s\r\n", tmp);
2659 g_free(tmp);
2661 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2662 g_free(to);
2663 g_free(hdr);
2666 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2667 SIPE_UNUSED_PARAMETER void *unused)
2669 gchar *key;
2670 struct sip_dialog *dialog;
2671 gchar *to = sip_uri_self(sip);
2672 gchar *tmp = get_contact(sip);
2673 gchar *hdr = g_strdup_printf(
2674 "Event: presence.wpending\r\n"
2675 "Accept: text/xml+msrtc.wpending\r\n"
2676 "Supported: com.microsoft.autoextend\r\n"
2677 "Supported: ms-benotify\r\n"
2678 "Proxy-Require: ms-benotify\r\n"
2679 "Supported: ms-piggyback-first-notify\r\n"
2680 "Contact: %s\r\n", tmp);
2681 g_free(tmp);
2683 /* Subscription is identified by <event> key */
2684 key = g_strdup_printf("<%s>", "presence.wpending");
2685 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2686 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2688 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2690 g_free(to);
2691 g_free(hdr);
2692 g_free(key);
2696 * Fires on deregistration event initiated by server.
2697 * [MS-SIPREGE] SIP extension.
2700 // 2007 Example
2702 // Content-Type: text/registration-event
2703 // subscription-state: terminated;expires=0
2704 // ms-diagnostics-public: 4141;reason="User disabled"
2706 // deregistered;event=rejected
2708 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2710 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2711 gchar *event = NULL;
2712 gchar *reason = NULL;
2713 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2714 gchar *warning;
2716 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2717 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2719 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2720 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2721 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2722 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2723 } else {
2724 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2725 return;
2728 if (diagnostics != NULL) {
2729 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2730 } else { // for LCS2005
2731 int error_id = 0;
2732 if (event && sipe_strcase_equal(event, "unregistered")) {
2733 error_id = 4140; // [MS-SIPREGE]
2734 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2735 reason = g_strdup(_("you are already signed in at another location"));
2736 } else if (event && sipe_strcase_equal(event, "rejected")) {
2737 error_id = 4141;
2738 reason = g_strdup(_("user disabled")); // [MS-OCER]
2739 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2740 error_id = 4142;
2741 reason = g_strdup(_("user moved")); // [MS-OCER]
2744 g_free(event);
2745 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2746 g_free(reason);
2748 sip->gc->wants_to_die = TRUE;
2749 purple_connection_error(sip->gc, warning);
2750 g_free(warning);
2754 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2756 xmlnode *xn_provision_group_list;
2757 xmlnode *node;
2759 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2761 /* provisionGroup */
2762 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2763 if (sipe_strequal("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2764 g_free(sip->focus_factory_uri);
2765 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2766 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2767 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2768 break;
2771 xmlnode_free(xn_provision_group_list);
2774 /** for 2005 system */
2775 static void
2776 sipe_process_provisioning(struct sipe_account_data *sip,
2777 struct sipmsg *msg)
2779 xmlnode *xn_provision;
2780 xmlnode *node;
2782 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2783 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2784 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2785 if ((node = xmlnode_get_child(node, "line"))) {
2786 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2787 const gchar *server = xmlnode_get_attrib(node, "server");
2788 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2789 sip_csta_open(sip, line_uri, server);
2792 xmlnode_free(xn_provision);
2795 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2797 const gchar *contacts_delta;
2798 xmlnode *xml;
2800 xml = xmlnode_from_str(msg->body, msg->bodylen);
2801 if (!xml)
2803 return;
2806 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2807 if (contacts_delta)
2809 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2812 xmlnode_free(xml);
2815 static void
2816 free_container(struct sipe_container *container)
2818 GSList *entry;
2820 if (!container) return;
2822 entry = container->members;
2823 while (entry) {
2824 void *data = entry->data;
2825 entry = g_slist_remove(entry, data);
2826 g_free(data);
2828 g_free(container);
2832 * Finds locally stored MS-PRES container member
2834 static struct sipe_container_member *
2835 sipe_find_container_member(struct sipe_container *container,
2836 const gchar *type,
2837 const gchar *value)
2839 struct sipe_container_member *member;
2840 GSList *entry;
2842 if (container == NULL || type == NULL) {
2843 return NULL;
2846 entry = container->members;
2847 while (entry) {
2848 member = entry->data;
2849 if (!g_strcasecmp(member->type, type)
2850 && ((!member->value && !value)
2851 || (value && member->value && !g_strcasecmp(member->value, value)))
2853 return member;
2855 entry = entry->next;
2857 return NULL;
2861 * Finds locally stored MS-PRES container by id
2863 static struct sipe_container *
2864 sipe_find_container(struct sipe_account_data *sip,
2865 guint id)
2867 struct sipe_container *container;
2868 GSList *entry;
2870 if (sip == NULL) {
2871 return NULL;
2874 entry = sip->containers;
2875 while (entry) {
2876 container = entry->data;
2877 if (id == container->id) {
2878 return container;
2880 entry = entry->next;
2882 return NULL;
2886 * Access Levels
2887 * 32000 - Blocked
2888 * 400 - Personal
2889 * 300 - Team
2890 * 200 - Company
2891 * 100 - Public
2893 static int
2894 sipe_find_access_level(struct sipe_account_data *sip,
2895 const gchar *type,
2896 const gchar *value)
2898 guint containers[] = {32000, 400, 300, 200, 100};
2899 int i = 0;
2901 for (i = 0; i < 5; i++) {
2902 struct sipe_container_member *member;
2903 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2904 if (!container) continue;
2906 member = sipe_find_container_member(container, type, value);
2907 if (member) {
2908 return containers[i];
2912 return -1;
2915 static void
2916 sipe_send_set_container_members(struct sipe_account_data *sip,
2917 guint container_id,
2918 guint container_version,
2919 const gchar* action,
2920 const gchar* type,
2921 const gchar* value)
2923 gchar *self = sip_uri_self(sip);
2924 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2925 gchar *contact;
2926 gchar *hdr;
2927 gchar *body = g_strdup_printf(
2928 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2929 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2930 "</setContainerMembers>",
2931 container_id,
2932 container_version,
2933 action,
2934 type,
2935 value_str);
2936 g_free(value_str);
2938 contact = get_contact(sip);
2939 hdr = g_strdup_printf("Contact: %s\r\n"
2940 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2941 g_free(contact);
2943 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2945 g_free(hdr);
2946 g_free(body);
2947 g_free(self);
2950 static void
2951 free_publication(struct sipe_publication *publication)
2953 g_free(publication->category);
2954 g_free(publication->cal_event_hash);
2955 g_free(publication->note);
2957 g_free(publication->working_hours_xml_str);
2958 g_free(publication->fb_start_str);
2959 g_free(publication->free_busy_base64);
2961 g_free(publication);
2964 /* key is <category><instance><container> */
2965 static gboolean
2966 sipe_is_our_publication(struct sipe_account_data *sip,
2967 const gchar *key)
2969 GSList *entry;
2971 /* filling keys for our publications if not yet cached */
2972 if (!sip->our_publication_keys) {
2973 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2974 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2975 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2976 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2977 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2978 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2979 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2981 purple_debug_info("sipe", "* Our Publication Instances *\n");
2982 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance, device_instance);
2983 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance, machine_instance);
2984 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance, user_instance);
2985 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance, calendar_instance);
2986 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance, cal_oof_instance);
2987 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance, cal_data_instance);
2988 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance, note_oof_instance);
2989 purple_debug_info("sipe", "\tNote : %u\n", 0);
2990 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2992 /* device */
2993 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2994 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2996 /* state:machineState */
2997 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2998 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2999 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3000 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3002 /* state:userState */
3003 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3004 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3005 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3006 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3008 /* state:calendarState */
3009 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3010 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3011 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3012 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3014 /* state:calendarState OOF */
3015 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3016 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3017 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3018 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3020 /* note */
3021 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3022 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3023 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3024 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3025 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3026 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3028 /* note OOF */
3029 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3030 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3031 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3032 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3033 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3034 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3036 /* calendarData:WorkingHours */
3037 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3038 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3039 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3040 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3041 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3042 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3043 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3044 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3045 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3046 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3047 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3048 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3050 /* calendarData:FreeBusy */
3051 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3052 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3053 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3054 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3055 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3056 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3057 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3058 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3059 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3060 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3061 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3062 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3064 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
3065 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3068 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3070 entry = sip->our_publication_keys;
3071 while (entry) {
3072 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3073 if (sipe_strequal(entry->data, key)) {
3074 return TRUE;
3076 entry = entry->next;
3078 return FALSE;
3081 /** Property names to store in blist.xml */
3082 #define ALIAS_PROP "alias"
3083 #define EMAIL_PROP "email"
3084 #define PHONE_PROP "phone"
3085 #define PHONE_DISPLAY_PROP "phone-display"
3086 #define PHONE_MOBILE_PROP "phone-mobile"
3087 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3088 #define PHONE_HOME_PROP "phone-home"
3089 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3090 #define PHONE_OTHER_PROP "phone-other"
3091 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3092 #define PHONE_CUSTOM1_PROP "phone-custom1"
3093 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3094 #define SITE_PROP "site"
3095 #define COMPANY_PROP "company"
3096 #define DEPARTMENT_PROP "department"
3097 #define TITLE_PROP "title"
3098 #define OFFICE_PROP "office"
3099 /** implies work address */
3100 #define ADDRESS_STREET_PROP "address-street"
3101 #define ADDRESS_CITY_PROP "address-city"
3102 #define ADDRESS_STATE_PROP "address-state"
3103 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3104 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3107 * Tries to figure out user first and last name
3108 * based on Display Name and email properties.
3110 * Allocates memory - must be g_free()'d
3112 * Examples to parse:
3113 * First Last
3114 * First Last - Company Name
3115 * Last, First
3116 * Last, First M.
3117 * Last, First (C)(STP) (Company)
3118 * first.last@company.com (preprocessed as "first last")
3119 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3121 * Unusable examples:
3122 * user@company.com (preprocessed as "user")
3123 * first.m.last@company.com (preprocessed as "first m last")
3124 * user.company.com@reuters.net (preprocessed as "user company com")
3126 static void
3127 sipe_get_first_last_names(struct sipe_account_data *sip,
3128 const char *uri,
3129 char **first_name,
3130 char **last_name)
3132 PurpleBuddy *p_buddy;
3133 char *display_name;
3134 const char *email;
3135 const char *first, *last;
3136 char *tmp;
3137 char **parts;
3138 gboolean has_comma = FALSE;
3140 if (!sip || !uri) return;
3142 p_buddy = purple_find_buddy(sip->account, uri);
3144 if (!p_buddy) return;
3146 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3147 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3149 if (!display_name && !email) return;
3151 /* if no display name, make "first last anything_else" out of email */
3152 if (email && !display_name) {
3153 display_name = g_strndup(email, strstr(email, "@") - email);
3154 display_name = purple_strreplace((tmp = display_name), ".", " ");
3155 g_free(tmp);
3158 if (display_name) {
3159 has_comma = (strstr(display_name, ",") != NULL);
3160 display_name = purple_strreplace((tmp = display_name), ", ", " ");
3161 g_free(tmp);
3162 display_name = purple_strreplace((tmp = display_name), ",", " ");
3163 g_free(tmp);
3166 parts = g_strsplit(display_name, " ", 0);
3168 if (!parts[0] || !parts[1]) {
3169 g_free(display_name);
3170 g_strfreev(parts);
3171 return;
3174 if (has_comma) {
3175 last = parts[0];
3176 first = parts[1];
3177 } else {
3178 first = parts[0];
3179 last = parts[1];
3182 if (first_name) {
3183 *first_name = g_strstrip(g_strdup(first));
3186 if (last_name) {
3187 *last_name = g_strstrip(g_strdup(last));
3190 g_free(display_name);
3191 g_strfreev(parts);
3195 * Update user information
3197 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3198 * @param property_name
3199 * @param property_value may be modified to strip white space
3201 static void
3202 sipe_update_user_info(struct sipe_account_data *sip,
3203 const char *uri,
3204 const char *property_name,
3205 char *property_value)
3207 GSList *buddies, *entry;
3209 if (!property_name || strlen(property_name) == 0) return;
3211 if (property_value)
3212 property_value = g_strstrip(property_value);
3214 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3215 while (entry) {
3216 const char *prop_str;
3217 const char *server_alias;
3218 PurpleBuddy *p_buddy = entry->data;
3220 /* for Display Name */
3221 if (sipe_strequal(property_name, ALIAS_PROP)) {
3222 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3223 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
3224 purple_blist_alias_buddy(p_buddy, property_value);
3227 server_alias = purple_buddy_get_server_alias(p_buddy);
3228 if (!is_empty(property_value) &&
3229 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3231 purple_blist_server_alias_buddy(p_buddy, property_value);
3234 /* for other properties */
3235 else {
3236 if (!is_empty(property_value)) {
3237 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3238 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3239 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3244 entry = entry->next;
3246 g_slist_free(buddies);
3250 * Update user phone
3251 * Suitable for both 2005 and 2007 systems.
3253 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3254 * @param phone_type
3255 * @param phone may be modified to strip white space
3256 * @param phone_display_string may be modified to strip white space
3258 static void
3259 sipe_update_user_phone(struct sipe_account_data *sip,
3260 const char *uri,
3261 const gchar *phone_type,
3262 gchar *phone,
3263 gchar *phone_display_string)
3265 const char *phone_node = PHONE_PROP; /* work phone by default */
3266 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3268 if(!phone || strlen(phone) == 0) return;
3270 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3271 phone_node = PHONE_MOBILE_PROP;
3272 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3273 } else if (sipe_strequal(phone_type, "home")) {
3274 phone_node = PHONE_HOME_PROP;
3275 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3276 } else if (sipe_strequal(phone_type, "other")) {
3277 phone_node = PHONE_OTHER_PROP;
3278 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3279 } else if (sipe_strequal(phone_type, "custom1")) {
3280 phone_node = PHONE_CUSTOM1_PROP;
3281 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3284 sipe_update_user_info(sip, uri, phone_node, phone);
3285 if (phone_display_string) {
3286 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3290 static void
3291 sipe_update_calendar(struct sipe_account_data *sip)
3293 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3295 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3297 if (sipe_strequal(calendar, "EXCH")) {
3298 sipe_ews_update_calendar(sip);
3301 /* schedule repeat */
3302 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3304 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3308 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3309 * by using standard Purple's means of signals and saved statuses.
3311 * Thus all UI elements get updated: Status Button with Note, docklet.
3312 * This is ablolutely important as both our status and note can come
3313 * inbound (roaming) or be updated programmatically (e.g. based on our
3314 * calendar data).
3316 static void
3317 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3318 const char *status_id,
3319 const char *message,
3320 time_t do_not_publish[])
3322 PurpleStatus *status = purple_account_get_active_status(account);
3323 gboolean changed = TRUE;
3325 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3326 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3328 changed = FALSE;
3331 if (purple_savedstatus_is_idleaway()) {
3332 changed = FALSE;
3335 if (changed) {
3336 PurpleSavedStatus *saved_status;
3337 const PurpleStatusType *acct_status_type =
3338 purple_status_type_find_with_id(account->status_types, status_id);
3339 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3340 sipe_activity activity = sipe_get_activity_by_token(status_id);
3342 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3343 if (saved_status) {
3344 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3347 /* If this type+message is unique then create a new transient saved status
3348 * Ref: gtkstatusbox.c
3350 if (!saved_status) {
3351 GList *tmp;
3352 GList *active_accts = purple_accounts_get_all_active();
3354 saved_status = purple_savedstatus_new(NULL, primitive);
3355 purple_savedstatus_set_message(saved_status, message);
3357 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3358 purple_savedstatus_set_substatus(saved_status,
3359 (PurpleAccount *)tmp->data, acct_status_type, message);
3361 g_list_free(active_accts);
3364 do_not_publish[activity] = time(NULL);
3365 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3366 status_id, (int)do_not_publish[activity]);
3368 /* Set the status for each account */
3369 purple_savedstatus_activate(saved_status);
3373 struct hash_table_delete_payload {
3374 GHashTable *hash_table;
3375 guint container;
3378 static void
3379 sipe_remove_category_container_publications_cb(const char *name,
3380 struct sipe_publication *publication,
3381 struct hash_table_delete_payload *payload)
3383 if (publication->container == payload->container) {
3384 g_hash_table_remove(payload->hash_table, name);
3387 static void
3388 sipe_remove_category_container_publications(GHashTable *our_publications,
3389 const char *category,
3390 guint container)
3392 struct hash_table_delete_payload payload;
3393 payload.hash_table = g_hash_table_lookup(our_publications, category);
3395 if (!payload.hash_table) return;
3397 payload.container = container;
3398 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3401 static void
3402 send_publish_category_initial(struct sipe_account_data *sip);
3405 * When we receive some self (BE) NOTIFY with a new subscriber
3406 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3409 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3411 gchar *contact;
3412 gchar *to;
3413 xmlnode *xml;
3414 xmlnode *node;
3415 xmlnode *node2;
3416 char *display_name = NULL;
3417 char *uri;
3418 GSList *category_names = NULL;
3419 int aggreg_avail = 0;
3420 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3421 gboolean do_update_status = FALSE;
3422 gboolean has_note_cleaned = FALSE;
3424 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3426 xml = xmlnode_from_str(msg->body, msg->bodylen);
3427 if (!xml) return;
3429 contact = get_contact(sip);
3430 to = sip_uri_self(sip);
3433 /* categories */
3434 /* set list of categories participating in this XML */
3435 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3436 const gchar *name = xmlnode_get_attrib(node, "name");
3437 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3439 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3440 category_names ? (int) g_slist_length(category_names) : -1);
3441 /* drop category information */
3442 if (category_names) {
3443 GSList *entry = category_names;
3444 while (entry) {
3445 GHashTable *cat_publications;
3446 const gchar *category = entry->data;
3447 entry = entry->next;
3448 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
3449 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3450 if (cat_publications) {
3451 g_hash_table_remove(sip->our_publications, category);
3452 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
3456 g_slist_free(category_names);
3457 /* filling our categories reflected in roaming data */
3458 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
3459 const char *tmp;
3460 const gchar *name = xmlnode_get_attrib(node, "name");
3461 guint container = xmlnode_get_int_attrib(node, "container", -1);
3462 guint instance = xmlnode_get_int_attrib(node, "instance", -1);
3463 guint version = xmlnode_get_int_attrib(node, "version", 0);
3464 time_t publish_time = (tmp = xmlnode_get_attrib(node, "publishTime")) ?
3465 sipe_utils_str_to_time(tmp) : 0;
3466 gchar *key;
3467 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3469 /* Ex. clear note: <category name="note"/> */
3470 if (container == (guint)-1) {
3471 g_free(sip->note);
3472 sip->note = NULL;
3473 do_update_status = TRUE;
3474 continue;
3477 /* Ex. clear note: <category name="note" container="200"/> */
3478 if (instance == (guint)-1) {
3479 if (container == 200) {
3480 g_free(sip->note);
3481 sip->note = NULL;
3482 do_update_status = TRUE;
3484 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name, container);
3485 sipe_remove_category_container_publications(
3486 sip->our_publications, name, container);
3487 continue;
3490 /* key is <category><instance><container> */
3491 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3492 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
3494 /* capture all userState publication for later clean up if required */
3495 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3496 xmlnode *xn_state = xmlnode_get_child(node, "state");
3498 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "userState")) {
3499 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3500 publication->category = g_strdup(name);
3501 publication->instance = instance;
3502 publication->container = container;
3503 publication->version = version;
3505 if (!sip->user_state_publications) {
3506 sip->user_state_publications = g_hash_table_new_full(
3507 g_str_hash, g_str_equal,
3508 g_free, (GDestroyNotify)free_publication);
3510 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3511 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3512 key, version);
3516 if (sipe_is_our_publication(sip, key)) {
3517 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3519 publication->category = g_strdup(name);
3520 publication->instance = instance;
3521 publication->container = container;
3522 publication->version = version;
3524 /* filling publication->availability */
3525 if (sipe_strequal(name, "state")) {
3526 xmlnode *xn_state = xmlnode_get_child(node, "state");
3527 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3529 if (xn_avail) {
3530 gchar *avail_str = xmlnode_get_data(xn_avail);
3531 if (avail_str) {
3532 publication->availability = atoi(avail_str);
3534 g_free(avail_str);
3536 /* for calendarState */
3537 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "calendarState")) {
3538 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3539 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3541 event->start_time = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "startTime"));
3542 if (xn_activity) {
3543 if (sipe_strequal(xmlnode_get_attrib(xn_activity, "token"),
3544 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3546 event->is_meeting = TRUE;
3549 event->subject = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingSubject"));
3550 event->location = xmlnode_get_data(xmlnode_get_child(xn_state, "meetingLocation"));
3552 publication->cal_event_hash = sipe_cal_event_hash(event);
3553 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3554 publication->cal_event_hash);
3555 sipe_cal_event_free(event);
3558 /* filling publication->note */
3559 if (sipe_strequal(name, "note")) {
3560 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
3562 if (!has_note_cleaned) {
3563 has_note_cleaned = TRUE;
3565 g_free(sip->note);
3566 sip->note = NULL;
3567 sip->note_since = publish_time;
3569 do_update_status = TRUE;
3572 g_free(publication->note);
3573 publication->note = NULL;
3574 if (xn_body) {
3575 char *tmp;
3577 publication->note = g_markup_escape_text((tmp = xmlnode_get_data(xn_body)), -1);
3578 g_free(tmp);
3579 if (publish_time >= sip->note_since) {
3580 g_free(sip->note);
3581 sip->note = g_strdup(publication->note);
3582 sip->note_since = publish_time;
3583 sip->is_oof_note = sipe_strequal(xmlnode_get_attrib(xn_body, "type"), "OOF");
3585 do_update_status = TRUE;
3590 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3591 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3592 xmlnode *xn_free_busy = xmlnode_get_descendant(node, "calendarData", "freeBusy", NULL);
3593 xmlnode *xn_working_hours = xmlnode_get_descendant(node, "calendarData", "WorkingHours", NULL);
3594 if (xn_free_busy) {
3595 publication->fb_start_str = g_strdup(xmlnode_get_attrib(xn_free_busy, "startTime"));
3596 publication->free_busy_base64 = xmlnode_get_data(xn_free_busy);
3598 if (xn_working_hours) {
3599 publication->working_hours_xml_str = xmlnode_to_str(xn_working_hours, NULL);
3603 if (!cat_publications) {
3604 cat_publications = g_hash_table_new_full(
3605 g_str_hash, g_str_equal,
3606 g_free, (GDestroyNotify)free_publication);
3607 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3608 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
3610 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3611 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
3613 g_free(key);
3615 /* aggregateState (not an our publication) from 2-nd container */
3616 if (sipe_strequal(name, "state") && container == 2) {
3617 xmlnode *xn_state = xmlnode_get_child(node, "state");
3619 if (xn_state && sipe_strequal(xmlnode_get_attrib(xn_state, "type"), "aggregateState")) {
3620 xmlnode *xn_avail = xmlnode_get_child(xn_state, "availability");
3621 xmlnode *xn_activity = xmlnode_get_child(xn_state, "activity");
3623 if (xn_avail) {
3624 gchar *avail_str = xmlnode_get_data(xn_avail);
3625 if (avail_str) {
3626 aggreg_avail = atoi(avail_str);
3628 g_free(avail_str);
3631 if (xn_activity) {
3632 const char *activity_token = xmlnode_get_attrib(xn_activity, "token");
3634 aggreg_activity = sipe_get_activity_by_token(activity_token);
3637 do_update_status = TRUE;
3641 /* userProperties published by server from AD */
3642 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3643 xmlnode *line;
3644 /* line, for Remote Call Control (RCC) */
3645 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
3646 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
3647 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
3648 gchar *line_uri;
3650 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3652 line_uri = xmlnode_get_data(line);
3653 if (line_uri) {
3654 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
3655 sip_csta_open(sip, line_uri, line_server);
3657 g_free(line_uri);
3659 break;
3663 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3664 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3666 /* containers */
3667 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
3668 guint id = xmlnode_get_int_attrib(node, "id", 0);
3669 struct sipe_container *container = sipe_find_container(sip, id);
3671 if (container) {
3672 sip->containers = g_slist_remove(sip->containers, container);
3673 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
3674 free_container(container);
3676 container = g_new0(struct sipe_container, 1);
3677 container->id = id;
3678 container->version = xmlnode_get_int_attrib(node, "version", 0);
3679 sip->containers = g_slist_append(sip->containers, container);
3680 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
3682 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
3683 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3684 member->type = xmlnode_get_attrib(node2, "type");
3685 member->value = xmlnode_get_attrib(node2, "value");
3686 container->members = g_slist_append(container->members, member);
3687 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3688 member->type, member->value ? member->value : "");
3692 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
3693 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
3694 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
3695 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
3696 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
3697 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
3698 /* initial set-up to let counterparties see your status */
3699 if (sameEnterpriseAL < 0) {
3700 struct sipe_container *container = sipe_find_container(sip, 200);
3701 guint version = container ? container->version : 0;
3702 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
3704 if (federatedAL < 0) {
3705 struct sipe_container *container = sipe_find_container(sip, 100);
3706 guint version = container ? container->version : 0;
3707 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
3709 sip->access_level_set = TRUE;
3712 /* subscribers */
3713 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
3714 const char *user;
3715 const char *acknowledged;
3716 gchar *hdr;
3717 gchar *body;
3719 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
3720 if (!user) continue;
3721 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
3722 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
3723 uri = sip_uri_from_name(user);
3725 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3727 acknowledged= xmlnode_get_attrib(node, "acknowledged");
3728 if(sipe_strcase_equal(acknowledged,"false")){
3729 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
3730 if (!purple_find_buddy(sip->account, uri)) {
3731 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3734 hdr = g_strdup_printf(
3735 "Contact: %s\r\n"
3736 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3738 body = g_strdup_printf(
3739 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3740 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3741 "</setSubscribers>", user);
3743 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3744 g_free(body);
3745 g_free(hdr);
3747 g_free(display_name);
3748 g_free(uri);
3751 g_free(contact);
3752 xmlnode_free(xml);
3754 /* Publish initial state if not yet.
3755 * Assuming this happens on initial responce to subscription to roaming-self
3756 * so we've already updated our roaming data in full.
3757 * Only for 2007+
3759 if (!sip->initial_state_published) {
3760 send_publish_category_initial(sip);
3761 sip->initial_state_published = TRUE;
3762 /* dalayed run */
3763 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
3764 do_update_status = FALSE;
3765 } else if (aggreg_avail) {
3767 g_free(sip->status);
3768 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3769 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3770 } else {
3771 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3775 if (do_update_status) {
3776 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip->status);
3777 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3780 g_free(to);
3783 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3785 gchar *to = sip_uri_self(sip);
3786 gchar *tmp = get_contact(sip);
3787 gchar *hdr = g_strdup_printf(
3788 "Event: vnd-microsoft-roaming-ACL\r\n"
3789 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3790 "Supported: com.microsoft.autoextend\r\n"
3791 "Supported: ms-benotify\r\n"
3792 "Proxy-Require: ms-benotify\r\n"
3793 "Supported: ms-piggyback-first-notify\r\n"
3794 "Contact: %s\r\n", tmp);
3795 g_free(tmp);
3797 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3798 g_free(to);
3799 g_free(hdr);
3803 * To request for presence information about the user, access level settings that have already been configured by the user
3804 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3805 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3808 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3810 gchar *to = sip_uri_self(sip);
3811 gchar *tmp = get_contact(sip);
3812 gchar *hdr = g_strdup_printf(
3813 "Event: vnd-microsoft-roaming-self\r\n"
3814 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3815 "Supported: ms-benotify\r\n"
3816 "Proxy-Require: ms-benotify\r\n"
3817 "Supported: ms-piggyback-first-notify\r\n"
3818 "Contact: %s\r\n"
3819 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3821 gchar *body=g_strdup(
3822 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3823 "<roaming type=\"categories\"/>"
3824 "<roaming type=\"containers\"/>"
3825 "<roaming type=\"subscribers\"/></roamingList>");
3827 g_free(tmp);
3828 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3829 g_free(body);
3830 g_free(to);
3831 g_free(hdr);
3835 * For 2005 version
3837 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3839 gchar *to = sip_uri_self(sip);
3840 gchar *tmp = get_contact(sip);
3841 gchar *hdr = g_strdup_printf(
3842 "Event: vnd-microsoft-provisioning\r\n"
3843 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3844 "Supported: com.microsoft.autoextend\r\n"
3845 "Supported: ms-benotify\r\n"
3846 "Proxy-Require: ms-benotify\r\n"
3847 "Supported: ms-piggyback-first-notify\r\n"
3848 "Expires: 0\r\n"
3849 "Contact: %s\r\n", tmp);
3851 g_free(tmp);
3852 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3853 g_free(to);
3854 g_free(hdr);
3857 /** Subscription for provisioning information to help with initial
3858 * configuration. This subscription is a one-time query (denoted by the Expires header,
3859 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3860 * configuration, meeting policies, and policy settings that Communicator must enforce.
3861 * TODO: for what we need this information.
3864 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3866 gchar *to = sip_uri_self(sip);
3867 gchar *tmp = get_contact(sip);
3868 gchar *hdr = g_strdup_printf(
3869 "Event: vnd-microsoft-provisioning-v2\r\n"
3870 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3871 "Supported: com.microsoft.autoextend\r\n"
3872 "Supported: ms-benotify\r\n"
3873 "Proxy-Require: ms-benotify\r\n"
3874 "Supported: ms-piggyback-first-notify\r\n"
3875 "Expires: 0\r\n"
3876 "Contact: %s\r\n"
3877 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3878 gchar *body = g_strdup(
3879 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3880 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3881 "<provisioningGroup name=\"ucPolicy\"/>"
3882 "</provisioningGroupList>");
3884 g_free(tmp);
3885 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3886 g_free(body);
3887 g_free(to);
3888 g_free(hdr);
3891 static void
3892 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3893 gpointer value, gpointer user_data)
3895 struct sip_subscription *subscription = value;
3896 struct sip_dialog *dialog = &subscription->dialog;
3897 struct sipe_account_data *sip = user_data;
3898 gchar *tmp = get_contact(sip);
3899 gchar *hdr = g_strdup_printf(
3900 "Event: %s\r\n"
3901 "Expires: 0\r\n"
3902 "Contact: %s\r\n", subscription->event, tmp);
3903 g_free(tmp);
3905 /* Rate limit to max. 25 requests per seconds */
3906 g_usleep(1000000 / 25);
3908 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3909 g_free(hdr);
3912 /* IM Session (INVITE and MESSAGE methods) */
3914 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3915 static gchar *
3916 get_end_points (struct sipe_account_data *sip,
3917 struct sip_session *session)
3919 gchar *res;
3921 if (session == NULL) {
3922 return NULL;
3925 res = g_strdup_printf("<sip:%s>", sip->username);
3927 SIPE_DIALOG_FOREACH {
3928 gchar *tmp = res;
3929 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3930 g_free(tmp);
3932 if (dialog->theirepid) {
3933 tmp = res;
3934 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3935 g_free(tmp);
3937 } SIPE_DIALOG_FOREACH_END;
3939 return res;
3942 static gboolean
3943 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3944 struct sipmsg *msg,
3945 SIPE_UNUSED_PARAMETER struct transaction *trans)
3947 gboolean ret = TRUE;
3949 if (msg->response != 200) {
3950 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3951 return FALSE;
3954 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3956 return ret;
3960 * Asks UA/proxy about its capabilities.
3962 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3964 gchar *to = sip_uri(who);
3965 gchar *contact = get_contact(sip);
3966 gchar *request = g_strdup_printf(
3967 "Accept: application/sdp\r\n"
3968 "Contact: %s\r\n", contact);
3969 g_free(contact);
3971 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3973 g_free(to);
3974 g_free(request);
3977 static void
3978 sipe_notify_user(struct sipe_account_data *sip,
3979 struct sip_session *session,
3980 PurpleMessageFlags flags,
3981 const gchar *message)
3983 PurpleConversation *conv;
3985 if (!session->conv) {
3986 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3987 } else {
3988 conv = session->conv;
3990 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3993 void
3994 sipe_present_info(struct sipe_account_data *sip,
3995 struct sip_session *session,
3996 const gchar *message)
3998 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4001 static void
4002 sipe_present_err(struct sipe_account_data *sip,
4003 struct sip_session *session,
4004 const gchar *message)
4006 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4009 void
4010 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4011 struct sip_session *session,
4012 int sip_error,
4013 int sip_warning,
4014 const gchar *who,
4015 const gchar *message)
4017 char *msg, *msg_tmp, *msg_tmp2;
4018 const char *label;
4020 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
4021 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4022 g_free(msg_tmp);
4023 /* Service unavailable; Server Internal Error; Server Time-out */
4024 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4025 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4026 g_free(msg);
4027 msg = NULL;
4028 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4029 label = _("This message was not delivered to %s because the service is not available");
4030 } else if (sip_error == 486) { /* Busy Here */
4031 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4032 } else if (sip_error == 415) { /* Unsupported media type */
4033 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4034 } else {
4035 label = _("This message was not delivered to %s because one or more recipients are offline");
4038 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4039 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4040 msg ? ":" : "",
4041 msg ? msg : "");
4042 sipe_present_err(sip, session, msg_tmp);
4043 g_free(msg_tmp2);
4044 g_free(msg_tmp);
4045 g_free(msg);
4049 static gboolean
4050 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4051 SIPE_UNUSED_PARAMETER struct transaction *trans)
4053 gboolean ret = TRUE;
4054 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4055 struct sip_session *session = sipe_session_find_im(sip, with);
4056 struct sip_dialog *dialog;
4057 gchar *cseq;
4058 char *key;
4059 struct queued_message *message;
4061 if (!session) {
4062 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
4063 g_free(with);
4064 return FALSE;
4067 dialog = sipe_dialog_find(session, with);
4068 if (!dialog) {
4069 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
4070 g_free(with);
4071 return FALSE;
4074 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4075 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4076 g_free(cseq);
4077 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4079 if (msg->response >= 400) {
4080 PurpleBuddy *pbuddy;
4081 const char *alias = with;
4082 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4083 int warning = -1;
4085 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
4087 if (warn_hdr) {
4088 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4089 if (parts[0]) {
4090 warning = atoi(parts[0]);
4092 g_strfreev(parts);
4095 /* cancel file transfer as rejected by server */
4096 if (msg->response == 606 && /* Not acceptable all. */
4097 warning == 309 && /* Message contents not allowed by policy */
4098 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4100 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4101 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4102 sipe_utils_nameval_free(parsed_body);
4105 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4106 alias = purple_buddy_get_alias(pbuddy);
4109 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4111 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4112 if (msg->response == 408 || /* Request timeout */
4113 msg->response == 480 || /* Temporarily Unavailable */
4114 msg->response == 481) { /* Call/Transaction Does Not Exist */
4115 purple_debug_info("sipe", "process_message_response: assuming dangling IM session, dropping it.\n");
4116 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4119 ret = FALSE;
4120 } else {
4121 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4122 if (message_id) {
4123 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4124 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4125 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4128 g_hash_table_remove(session->unconfirmed_messages, key);
4129 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4130 key, g_hash_table_size(session->unconfirmed_messages));
4133 g_free(key);
4134 g_free(with);
4136 if (ret) sipe_im_process_queue(sip, session);
4137 return ret;
4140 static gboolean
4141 sipe_is_election_finished(struct sip_session *session);
4143 static void
4144 sipe_election_result(struct sipe_account_data *sip,
4145 void *sess);
4147 static gboolean
4148 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4149 SIPE_UNUSED_PARAMETER struct transaction *trans)
4151 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4152 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4153 struct sip_dialog *dialog;
4154 struct sip_session *session;
4156 session = sipe_session_find_chat_by_callid(sip, callid);
4157 if (!session) {
4158 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
4159 return FALSE;
4162 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4163 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4164 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
4165 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
4167 if (xn_request_rm_response) {
4168 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
4169 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
4171 dialog = sipe_dialog_find(session, with);
4172 if (!dialog) {
4173 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
4174 xmlnode_free(xn_action);
4175 return FALSE;
4178 if (allow && !g_strcasecmp(allow, "true")) {
4179 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
4180 dialog->election_vote = 1;
4181 } else if (allow && !g_strcasecmp(allow, "false")) {
4182 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
4183 dialog->election_vote = -1;
4186 if (sipe_is_election_finished(session)) {
4187 sipe_election_result(sip, session);
4190 } else if (xn_set_rm_response) {
4193 xmlnode_free(xn_action);
4197 return TRUE;
4200 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4202 gchar *hdr;
4203 gchar *tmp;
4204 char *msgtext = NULL;
4205 const gchar *msgr = "";
4206 gchar *tmp2 = NULL;
4208 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4209 char *msgformat;
4210 gchar *msgr_value;
4212 sipe_parse_html(msg, &msgformat, &msgtext);
4213 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
4215 msgr_value = sipmsg_get_msgr_string(msgformat);
4216 g_free(msgformat);
4217 if (msgr_value) {
4218 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4219 g_free(msgr_value);
4221 } else {
4222 msgtext = g_strdup(msg);
4225 tmp = get_contact(sip);
4226 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4227 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4228 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4229 if (content_type == NULL)
4230 content_type = "text/plain";
4232 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4233 g_free(tmp);
4234 g_free(tmp2);
4236 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4237 g_free(msgtext);
4238 g_free(hdr);
4242 void
4243 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4245 GSList *entry2 = session->outgoing_message_queue;
4246 while (entry2) {
4247 struct queued_message *msg = entry2->data;
4249 /* for multiparty chat or conference */
4250 if (session->is_multiparty || session->focus_uri) {
4251 gchar *who = sip_uri_self(sip);
4252 serv_got_chat_in(sip->gc, session->chat_id, who,
4253 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4254 g_free(who);
4257 SIPE_DIALOG_FOREACH {
4258 char *key;
4259 struct queued_message *message;
4261 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4263 message = g_new0(struct queued_message,1);
4264 message->body = g_strdup(msg->body);
4265 if (msg->content_type != NULL)
4266 message->content_type = g_strdup(msg->content_type);
4268 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4269 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4270 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4271 key, g_hash_table_size(session->unconfirmed_messages));
4272 g_free(key);
4274 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4275 } SIPE_DIALOG_FOREACH_END;
4277 entry2 = sipe_session_dequeue_message(session);
4281 static void
4282 sipe_refer_notify(struct sipe_account_data *sip,
4283 struct sip_session *session,
4284 const gchar *who,
4285 int status,
4286 const gchar *desc)
4288 gchar *hdr;
4289 gchar *body;
4290 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4292 hdr = g_strdup_printf(
4293 "Event: refer\r\n"
4294 "Subscription-State: %s\r\n"
4295 "Content-Type: message/sipfrag\r\n",
4296 status >= 200 ? "terminated" : "active");
4298 body = g_strdup_printf(
4299 "SIP/2.0 %d %s\r\n",
4300 status, desc);
4302 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4304 g_free(hdr);
4305 g_free(body);
4308 static gboolean
4309 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4311 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4312 struct sip_session *session;
4313 struct sip_dialog *dialog;
4314 char *cseq;
4315 char *key;
4316 struct queued_message *message;
4317 struct sipmsg *request_msg = trans->msg;
4319 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4320 gchar *referred_by;
4322 session = sipe_session_find_chat_by_callid(sip, callid);
4323 if (!session) {
4324 session = sipe_session_find_im(sip, with);
4326 if (!session) {
4327 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4328 g_free(with);
4329 return FALSE;
4332 dialog = sipe_dialog_find(session, with);
4333 if (!dialog) {
4334 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4335 g_free(with);
4336 return FALSE;
4339 sipe_dialog_parse(dialog, msg, TRUE);
4341 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4342 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4343 g_free(cseq);
4344 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4346 if (msg->response != 200) {
4347 PurpleBuddy *pbuddy;
4348 const char *alias = with;
4349 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4350 int warning = -1;
4352 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4354 if (warn_hdr) {
4355 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4356 if (parts[0]) {
4357 warning = atoi(parts[0]);
4359 g_strfreev(parts);
4362 /* cancel file transfer as rejected by server */
4363 if (msg->response == 606 && /* Not acceptable all. */
4364 warning == 309 && /* Message contents not allowed by policy */
4365 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4367 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4368 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4369 sipe_utils_nameval_free(parsed_body);
4372 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4373 alias = purple_buddy_get_alias(pbuddy);
4376 if (message) {
4377 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4378 } else {
4379 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4380 sipe_present_err(sip, session, tmp_msg);
4381 g_free(tmp_msg);
4384 sipe_dialog_remove(session, with);
4386 g_free(key);
4387 g_free(with);
4388 return FALSE;
4391 dialog->cseq = 0;
4392 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4393 dialog->outgoing_invite = NULL;
4394 dialog->is_established = TRUE;
4396 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4397 if (referred_by) {
4398 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4399 g_free(referred_by);
4402 /* add user to chat if it is a multiparty session */
4403 if (session->is_multiparty) {
4404 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4405 with, NULL,
4406 PURPLE_CBFLAGS_NONE, TRUE);
4409 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4410 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4411 sipe_session_dequeue_message(session);
4414 sipe_im_process_queue(sip, session);
4416 g_hash_table_remove(session->unconfirmed_messages, key);
4417 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4418 key, g_hash_table_size(session->unconfirmed_messages));
4420 g_free(key);
4421 g_free(with);
4422 return TRUE;
4426 void
4427 sipe_invite(struct sipe_account_data *sip,
4428 struct sip_session *session,
4429 const gchar *who,
4430 const gchar *msg_body,
4431 const gchar *msg_content_type,
4432 const gchar *referred_by,
4433 const gboolean is_triggered)
4435 gchar *hdr;
4436 gchar *to;
4437 gchar *contact;
4438 gchar *body;
4439 gchar *self;
4440 char *ms_text_format = NULL;
4441 gchar *roster_manager;
4442 gchar *end_points;
4443 gchar *referred_by_str;
4444 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4446 if (dialog && dialog->is_established) {
4447 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
4448 return;
4451 if (!dialog) {
4452 dialog = sipe_dialog_add(session);
4453 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4454 dialog->with = g_strdup(who);
4457 if (!(dialog->ourtag)) {
4458 dialog->ourtag = gentag();
4461 to = sip_uri(who);
4463 if (msg_body) {
4464 char *msgtext = NULL;
4465 char *base64_msg;
4466 const gchar *msgr = "";
4467 char *key;
4468 struct queued_message *message;
4469 gchar *tmp = NULL;
4471 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4472 char *msgformat;
4473 gchar *msgr_value;
4475 sipe_parse_html(msg_body, &msgformat, &msgtext);
4476 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
4478 msgr_value = sipmsg_get_msgr_string(msgformat);
4479 g_free(msgformat);
4480 if (msgr_value) {
4481 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4482 g_free(msgr_value);
4484 } else {
4485 msgtext = g_strdup(msg_body);
4488 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4489 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4490 msg_content_type ? msg_content_type : "text/plain",
4491 msgr,
4492 base64_msg);
4493 g_free(msgtext);
4494 g_free(tmp);
4495 g_free(base64_msg);
4497 message = g_new0(struct queued_message,1);
4498 message->body = g_strdup(msg_body);
4499 if (msg_content_type != NULL)
4500 message->content_type = g_strdup(msg_content_type);
4502 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4503 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4504 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4505 key, g_hash_table_size(session->unconfirmed_messages));
4506 g_free(key);
4509 contact = get_contact(sip);
4510 end_points = get_end_points(sip, session);
4511 self = sip_uri_self(sip);
4512 roster_manager = g_strdup_printf(
4513 "Roster-Manager: %s\r\n"
4514 "EndPoints: %s\r\n",
4515 self,
4516 end_points);
4517 referred_by_str = referred_by ?
4518 g_strdup_printf(
4519 "Referred-By: %s\r\n",
4520 referred_by)
4521 : g_strdup("");
4522 hdr = g_strdup_printf(
4523 "Supported: ms-sender\r\n"
4524 "%s"
4525 "%s"
4526 "%s"
4527 "%s"
4528 "Contact: %s\r\n%s"
4529 "Content-Type: application/sdp\r\n",
4530 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4531 referred_by_str,
4532 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4533 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4534 contact,
4535 ms_text_format ? ms_text_format : "");
4536 g_free(ms_text_format);
4537 g_free(self);
4539 body = g_strdup_printf(
4540 "v=0\r\n"
4541 "o=- 0 0 IN IP4 %s\r\n"
4542 "s=session\r\n"
4543 "c=IN IP4 %s\r\n"
4544 "t=0 0\r\n"
4545 "m=%s %d sip null\r\n"
4546 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4547 purple_network_get_my_ip(-1),
4548 purple_network_get_my_ip(-1),
4549 sip->ocs2007 ? "message" : "x-ms-message",
4550 sip->realport);
4552 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4553 to, to, hdr, body, dialog, process_invite_response);
4555 g_free(to);
4556 g_free(roster_manager);
4557 g_free(end_points);
4558 g_free(referred_by_str);
4559 g_free(body);
4560 g_free(hdr);
4561 g_free(contact);
4564 static void
4565 sipe_refer(struct sipe_account_data *sip,
4566 struct sip_session *session,
4567 const gchar *who)
4569 gchar *hdr;
4570 gchar *contact;
4571 gchar *epid = get_epid(sip);
4572 struct sip_dialog *dialog = sipe_dialog_find(session,
4573 session->roster_manager);
4574 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4576 contact = get_contact(sip);
4577 hdr = g_strdup_printf(
4578 "Contact: %s\r\n"
4579 "Refer-to: <%s>\r\n"
4580 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4581 "Require: com.microsoft.rtc-multiparty\r\n",
4582 contact,
4583 who,
4584 sip->username,
4585 ourtag ? ";tag=" : "",
4586 ourtag ? ourtag : "",
4587 epid);
4588 g_free(epid);
4590 send_sip_request(sip->gc, "REFER",
4591 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4593 g_free(hdr);
4594 g_free(contact);
4597 static void
4598 sipe_send_election_request_rm(struct sipe_account_data *sip,
4599 struct sip_dialog *dialog,
4600 int bid)
4602 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4604 gchar *body = g_strdup_printf(
4605 "<?xml version=\"1.0\"?>\r\n"
4606 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4607 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4608 sip->username, bid);
4610 send_sip_request(sip->gc, "INFO",
4611 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4613 g_free(body);
4616 static void
4617 sipe_send_election_set_rm(struct sipe_account_data *sip,
4618 struct sip_dialog *dialog)
4620 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4622 gchar *body = g_strdup_printf(
4623 "<?xml version=\"1.0\"?>\r\n"
4624 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4625 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4626 sip->username);
4628 send_sip_request(sip->gc, "INFO",
4629 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4631 g_free(body);
4634 static void
4635 sipe_session_close(struct sipe_account_data *sip,
4636 struct sip_session * session)
4638 if (session && session->focus_uri) {
4639 sipe_conf_immcu_closed(sip, session);
4640 conf_session_close(sip, session);
4643 if (session) {
4644 SIPE_DIALOG_FOREACH {
4645 /* @TODO slow down BYE message sending rate */
4646 /* @see single subscription code */
4647 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4648 } SIPE_DIALOG_FOREACH_END;
4650 sipe_session_remove(sip, session);
4654 static void
4655 sipe_session_close_all(struct sipe_account_data *sip)
4657 GSList *entry;
4658 while ((entry = sip->sessions) != NULL) {
4659 sipe_session_close(sip, entry->data);
4663 static void
4664 sipe_convo_closed(PurpleConnection * gc, const char *who)
4666 struct sipe_account_data *sip = gc->proto_data;
4668 purple_debug_info("sipe", "conversation with %s closed\n", who);
4669 sipe_session_close(sip, sipe_session_find_im(sip, who));
4672 static void
4673 sipe_chat_leave (PurpleConnection *gc, int id)
4675 struct sipe_account_data *sip = gc->proto_data;
4676 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4678 sipe_session_close(sip, session);
4681 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4682 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4684 struct sipe_account_data *sip = gc->proto_data;
4685 struct sip_session *session;
4686 struct sip_dialog *dialog;
4687 gchar *uri = sip_uri(who);
4689 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
4691 session = sipe_session_find_or_add_im(sip, uri);
4692 dialog = sipe_dialog_find(session, uri);
4694 // Queue the message
4695 sipe_session_enqueue_message(session, what, NULL);
4697 if (dialog && !dialog->outgoing_invite) {
4698 sipe_im_process_queue(sip, session);
4699 } else if (!dialog || !dialog->outgoing_invite) {
4700 // Need to send the INVITE to get the outgoing dialog setup
4701 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4704 g_free(uri);
4705 return 1;
4708 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4709 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4711 struct sipe_account_data *sip = gc->proto_data;
4712 struct sip_session *session;
4714 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
4716 session = sipe_session_find_chat_by_id(sip, id);
4718 // Queue the message
4719 if (session && session->dialogs) {
4720 sipe_session_enqueue_message(session,what,NULL);
4721 sipe_im_process_queue(sip, session);
4722 } else if (sip) {
4723 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4724 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4726 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
4727 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
4729 if (sip->ocs2007) {
4730 struct sip_session *session = sipe_session_add_chat(sip);
4732 session->is_multiparty = FALSE;
4733 session->focus_uri = g_strdup(proto_chat_id);
4734 sipe_session_enqueue_message(session, what, NULL);
4735 sipe_invite_conf_focus(sip, session);
4739 return 1;
4742 /* End IM Session (INVITE and MESSAGE methods) */
4744 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4746 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4747 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4748 gchar *from;
4749 struct sip_session *session;
4751 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
4753 /* Call Control protocol */
4754 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4756 process_incoming_info_csta(sip, msg);
4757 return;
4760 from = parse_from(sipmsg_find_header(msg, "From"));
4761 session = sipe_session_find_chat_by_callid(sip, callid);
4762 if (!session) {
4763 session = sipe_session_find_im(sip, from);
4765 if (!session) {
4766 g_free(from);
4767 return;
4770 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4772 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
4773 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
4774 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
4776 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4778 if (xn_request_rm) {
4779 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4780 int bid = xmlnode_get_int_attrib(xn_request_rm, "bid", 0);
4781 gchar *body = g_strdup_printf(
4782 "<?xml version=\"1.0\"?>\r\n"
4783 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4784 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4785 sip->username,
4786 session->bid < bid ? "true" : "false");
4787 send_sip_response(sip->gc, msg, 200, "OK", body);
4788 g_free(body);
4789 } else if (xn_set_rm) {
4790 gchar *body;
4791 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
4792 g_free(session->roster_manager);
4793 session->roster_manager = g_strdup(rm);
4795 body = g_strdup_printf(
4796 "<?xml version=\"1.0\"?>\r\n"
4797 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4798 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4799 sip->username);
4800 send_sip_response(sip->gc, msg, 200, "OK", body);
4801 g_free(body);
4803 xmlnode_free(xn_action);
4806 else
4808 /* looks like purple lacks typing notification for chat */
4809 if (!session->is_multiparty && !session->focus_uri) {
4810 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
4811 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
4812 "status");
4813 if (sipe_strequal(status, "type")) {
4814 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4815 } else if (sipe_strequal(status, "idle")) {
4816 serv_got_typing_stopped(sip->gc, from);
4818 xmlnode_free(xn_keyboard_activity);
4821 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4823 g_free(from);
4826 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4828 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4829 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4830 struct sip_session *session;
4831 struct sip_dialog *dialog;
4833 /* collect dialog identification
4834 * we need callid, ourtag and theirtag to unambiguously identify dialog
4836 /* take data before 'msg' will be modified by send_sip_response */
4837 dialog = g_new0(struct sip_dialog, 1);
4838 dialog->callid = g_strdup(callid);
4839 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4840 dialog->with = g_strdup(from);
4841 sipe_dialog_parse(dialog, msg, FALSE);
4843 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4845 session = sipe_session_find_chat_by_callid(sip, callid);
4846 if (!session) {
4847 session = sipe_session_find_im(sip, from);
4849 if (!session) {
4850 sipe_dialog_free(dialog);
4851 g_free(from);
4852 return;
4855 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4856 g_free(session->roster_manager);
4857 session->roster_manager = NULL;
4860 /* This what BYE is essentially for - terminating dialog */
4861 sipe_dialog_remove_3(session, dialog);
4862 sipe_dialog_free(dialog);
4863 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4864 sipe_conf_immcu_closed(sip, session);
4865 } else if (session->is_multiparty) {
4866 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4869 g_free(from);
4872 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4874 gchar *self = sip_uri_self(sip);
4875 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4876 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4877 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4878 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4879 struct sip_session *session;
4880 struct sip_dialog *dialog;
4882 session = sipe_session_find_chat_by_callid(sip, callid);
4883 dialog = sipe_dialog_find(session, from);
4885 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
4886 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4887 } else {
4888 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4890 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4893 g_free(self);
4894 g_free(from);
4895 g_free(refer_to);
4896 g_free(referred_by);
4899 static unsigned int
4900 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4902 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
4903 struct sip_session *session;
4904 struct sip_dialog *dialog;
4906 if (state == PURPLE_NOT_TYPING)
4907 return 0;
4909 session = sipe_session_find_im(sip, who);
4910 dialog = sipe_dialog_find(session, who);
4912 if (session && dialog && dialog->is_established) {
4913 send_sip_request(gc, "INFO", who, who,
4914 "Content-Type: application/xml\r\n",
4915 SIPE_SEND_TYPING, dialog, NULL);
4917 return SIPE_TYPING_SEND_TIMEOUT;
4920 static gboolean resend_timeout(struct sipe_account_data *sip)
4922 GSList *tmp = sip->transactions;
4923 time_t currtime = time(NULL);
4924 while (tmp) {
4925 struct transaction *trans = tmp->data;
4926 tmp = tmp->next;
4927 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4928 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4929 /* TODO 408 */
4930 } else {
4931 if ((currtime - trans->time > 2) && trans->retries == 0) {
4932 trans->retries++;
4933 sendout_sipmsg(sip, trans->msg);
4937 return TRUE;
4940 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4941 SIPE_UNUSED_PARAMETER void *unused)
4943 /* register again when security token expires */
4944 /* we have to start a new authentication as the security token
4945 * is almost expired by sending a not signed REGISTER message */
4946 purple_debug_info("sipe", "do a full reauthentication\n");
4947 sipe_auth_free(&sip->registrar);
4948 sipe_auth_free(&sip->proxy);
4949 sip->registerstatus = 0;
4950 do_register(sip);
4951 sip->reauthenticate_set = FALSE;
4954 static gboolean
4955 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4956 struct sipmsg *msg,
4957 GSList *parsed_body)
4959 gboolean found = FALSE;
4961 if (parsed_body) {
4962 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4964 if (sipe_strequal(invitation_command, "INVITE")) {
4965 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4966 found = TRUE;
4967 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4968 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4969 found = TRUE;
4970 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4971 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4972 found = TRUE;
4975 return found;
4978 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4980 gchar *from;
4981 const gchar *contenttype;
4982 gboolean found = FALSE;
4984 from = parse_from(sipmsg_find_header(msg, "From"));
4986 if (!from) return;
4988 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4990 contenttype = sipmsg_find_header(msg, "Content-Type");
4991 if (g_str_has_prefix(contenttype, "text/plain")
4992 || g_str_has_prefix(contenttype, "text/html")
4993 || g_str_has_prefix(contenttype, "multipart/related")
4994 || g_str_has_prefix(contenttype, "multipart/alternative"))
4996 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4997 gchar *html = get_html_message(contenttype, msg->body);
4999 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5000 if (!session) {
5001 session = sipe_session_find_im(sip, from);
5004 if (session && session->focus_uri) { /* a conference */
5005 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5006 gchar *sender = parse_from(tmp);
5007 g_free(tmp);
5008 serv_got_chat_in(sip->gc, session->chat_id, sender,
5009 PURPLE_MESSAGE_RECV, html, time(NULL));
5010 g_free(sender);
5011 } else if (session && session->is_multiparty) { /* a multiparty chat */
5012 serv_got_chat_in(sip->gc, session->chat_id, from,
5013 PURPLE_MESSAGE_RECV, html, time(NULL));
5014 } else {
5015 serv_got_im(sip->gc, from, html, 0, time(NULL));
5017 g_free(html);
5018 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5019 found = TRUE;
5021 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5022 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
5023 xmlnode *state;
5024 gchar *statedata;
5026 if (!isc) {
5027 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
5028 g_free(from);
5029 return;
5032 state = xmlnode_get_child(isc, "state");
5034 if (!state) {
5035 purple_debug_info("sipe", "process_incoming_message: no state found\n");
5036 xmlnode_free(isc);
5037 g_free(from);
5038 return;
5041 statedata = xmlnode_get_data(state);
5042 if (statedata) {
5043 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5044 else serv_got_typing_stopped(sip->gc, from);
5046 g_free(statedata);
5048 xmlnode_free(isc);
5049 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5050 found = TRUE;
5051 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5052 GSList *body = sipe_ft_parse_msg_body(msg->body);
5053 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5054 sipe_utils_nameval_free(body);
5055 if (found) {
5056 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5059 if (!found) {
5060 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5061 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5062 if (!session) {
5063 session = sipe_session_find_im(sip, from);
5065 if (session) {
5066 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5067 from);
5068 sipe_present_err(sip, session, errmsg);
5069 g_free(errmsg);
5072 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype);
5073 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5075 g_free(from);
5078 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5080 gchar *body;
5081 gchar *newTag;
5082 const gchar *oldHeader;
5083 gchar *newHeader;
5084 gboolean is_multiparty = FALSE;
5085 gboolean is_triggered = FALSE;
5086 gboolean was_multiparty = TRUE;
5087 gboolean just_joined = FALSE;
5088 gchar *from;
5089 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5090 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5091 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5092 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5093 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5094 GSList *end_points = NULL;
5095 char *tmp = NULL;
5096 struct sip_session *session;
5097 const gchar *ms_text_format;
5099 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
5100 g_free(tmp);
5102 /* Invitation to join conference */
5103 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5104 process_incoming_invite_conf(sip, msg);
5105 return;
5108 /* Only accept text invitations */
5109 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5110 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5111 return;
5114 // TODO There *must* be a better way to clean up the To header to add a tag...
5115 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
5116 oldHeader = sipmsg_find_header(msg, "To");
5117 newTag = gentag();
5118 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5119 sipmsg_remove_header_now(msg, "To");
5120 sipmsg_add_header_now(msg, "To", newHeader);
5121 g_free(newHeader);
5123 if (end_points_hdr) {
5124 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5126 if (g_slist_length(end_points) > 2) {
5127 is_multiparty = TRUE;
5130 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5131 is_triggered = TRUE;
5132 is_multiparty = TRUE;
5135 session = sipe_session_find_chat_by_callid(sip, callid);
5136 /* Convert to multiparty */
5137 if (session && is_multiparty && !session->is_multiparty) {
5138 g_free(session->with);
5139 session->with = NULL;
5140 was_multiparty = FALSE;
5141 session->is_multiparty = TRUE;
5142 session->chat_id = rand();
5145 if (!session && is_multiparty) {
5146 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5148 /* IM session */
5149 from = parse_from(sipmsg_find_header(msg, "From"));
5150 if (!session) {
5151 session = sipe_session_find_or_add_im(sip, from);
5154 if (session) {
5155 g_free(session->callid);
5156 session->callid = g_strdup(callid);
5158 session->is_multiparty = is_multiparty;
5159 if (roster_manager) {
5160 session->roster_manager = g_strdup(roster_manager);
5164 if (is_multiparty && end_points) {
5165 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5166 GSList *entry = end_points;
5167 while (entry) {
5168 struct sip_dialog *dialog;
5169 struct sipendpoint *end_point = entry->data;
5170 entry = entry->next;
5172 if (!g_strcasecmp(from, end_point->contact) ||
5173 !g_strcasecmp(to, end_point->contact))
5174 continue;
5176 dialog = sipe_dialog_find(session, end_point->contact);
5177 if (dialog) {
5178 g_free(dialog->theirepid);
5179 dialog->theirepid = end_point->epid;
5180 end_point->epid = NULL;
5181 } else {
5182 dialog = sipe_dialog_add(session);
5184 dialog->callid = g_strdup(session->callid);
5185 dialog->with = end_point->contact;
5186 end_point->contact = NULL;
5187 dialog->theirepid = end_point->epid;
5188 end_point->epid = NULL;
5190 just_joined = TRUE;
5192 /* send triggered INVITE */
5193 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5196 g_free(to);
5199 if (end_points) {
5200 GSList *entry = end_points;
5201 while (entry) {
5202 struct sipendpoint *end_point = entry->data;
5203 entry = entry->next;
5204 g_free(end_point->contact);
5205 g_free(end_point->epid);
5206 g_free(end_point);
5208 g_slist_free(end_points);
5211 if (session) {
5212 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5213 if (dialog) {
5214 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5215 sipe_dialog_parse_routes(dialog, msg, FALSE);
5216 } else {
5217 dialog = sipe_dialog_add(session);
5219 dialog->callid = g_strdup(session->callid);
5220 dialog->with = g_strdup(from);
5221 sipe_dialog_parse(dialog, msg, FALSE);
5223 if (!dialog->ourtag) {
5224 dialog->ourtag = newTag;
5225 newTag = NULL;
5228 just_joined = TRUE;
5230 } else {
5231 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5233 g_free(newTag);
5235 if (is_multiparty && !session->conv) {
5236 gchar *chat_title = sipe_chat_get_name(callid);
5237 gchar *self = sip_uri_self(sip);
5238 /* create prpl chat */
5239 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5240 session->chat_title = g_strdup(chat_title);
5241 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5242 /* add self */
5243 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5244 self, NULL,
5245 PURPLE_CBFLAGS_NONE, FALSE);
5246 g_free(chat_title);
5247 g_free(self);
5250 if (is_multiparty && !was_multiparty) {
5251 /* add current IM counterparty to chat */
5252 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5253 sipe_dialog_first(session)->with, NULL,
5254 PURPLE_CBFLAGS_NONE, FALSE);
5257 /* add inviting party to chat */
5258 if (just_joined && session->conv) {
5259 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5260 from, NULL,
5261 PURPLE_CBFLAGS_NONE, TRUE);
5264 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5266 /* This used only in 2005 official client, not 2007 or Reuters.
5267 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5268 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5270 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5271 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5272 if (is_multiparty ||
5273 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5275 if (ms_text_format) {
5276 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5278 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5279 if (tmp) {
5280 gsize len;
5281 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5283 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5285 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5286 sipe_utils_nameval_free(parsed_body);
5287 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5289 g_free(tmp);
5291 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5293 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5294 gchar *html = get_html_message(ms_text_format, NULL);
5295 if (html) {
5296 if (is_multiparty) {
5297 serv_got_chat_in(sip->gc, session->chat_id, from,
5298 PURPLE_MESSAGE_RECV, html, time(NULL));
5299 } else {
5300 serv_got_im(sip->gc, from, html, 0, time(NULL));
5302 g_free(html);
5303 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5309 g_free(from);
5311 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5312 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5313 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5315 body = g_strdup_printf(
5316 "v=0\r\n"
5317 "o=- 0 0 IN IP4 %s\r\n"
5318 "s=session\r\n"
5319 "c=IN IP4 %s\r\n"
5320 "t=0 0\r\n"
5321 "m=%s %d sip sip:%s\r\n"
5322 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5323 purple_network_get_my_ip(-1),
5324 purple_network_get_my_ip(-1),
5325 sip->ocs2007 ? "message" : "x-ms-message",
5326 sip->realport,
5327 sip->username);
5328 send_sip_response(sip->gc, msg, 200, "OK", body);
5329 g_free(body);
5332 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5334 gchar *body;
5336 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5337 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5338 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5340 body = g_strdup_printf(
5341 "v=0\r\n"
5342 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5343 "s=session\r\n"
5344 "c=IN IP4 0.0.0.0\r\n"
5345 "t=0 0\r\n"
5346 "m=%s %d sip sip:%s\r\n"
5347 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5348 sip->ocs2007 ? "message" : "x-ms-message",
5349 sip->realport,
5350 sip->username);
5351 send_sip_response(sip->gc, msg, 200, "OK", body);
5352 g_free(body);
5355 static const char*
5356 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5358 const char *res = "NTLM";
5359 #ifdef HAVE_KERBEROS
5360 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5361 res = "Kerberos";
5363 #else
5364 (void) sip; /* make compiler happy */
5365 #endif
5366 return res;
5369 static void sipe_connection_cleanup(struct sipe_account_data *);
5370 static void create_connection(struct sipe_account_data *, gchar *, int);
5372 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5373 SIPE_UNUSED_PARAMETER struct transaction *trans)
5375 gchar *tmp;
5376 const gchar *expires_header;
5377 int expires, i;
5378 GSList *hdr = msg->headers;
5379 struct sipnameval *elem;
5381 expires_header = sipmsg_find_header(msg, "Expires");
5382 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5383 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
5385 switch (msg->response) {
5386 case 200:
5387 if (expires == 0) {
5388 sip->registerstatus = 0;
5389 } else {
5390 const gchar *contact_hdr;
5391 gchar *gruu = NULL;
5392 gchar *epid;
5393 gchar *uuid;
5394 gchar *timeout;
5395 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5396 const char *auth_scheme;
5398 if (!sip->reregister_set) {
5399 gchar *action_name = g_strdup_printf("<%s>", "registration");
5400 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5401 g_free(action_name);
5402 sip->reregister_set = TRUE;
5405 sip->registerstatus = 3;
5407 if (server_hdr && !sip->server_version) {
5408 sip->server_version = g_strdup(server_hdr);
5409 g_free(default_ua);
5410 default_ua = NULL;
5413 auth_scheme = sipe_get_auth_scheme_name(sip);
5414 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5416 if (tmp) {
5417 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
5418 fill_auth(tmp, &sip->registrar);
5421 if (!sip->reauthenticate_set) {
5422 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5423 guint reauth_timeout;
5424 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5425 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5426 reauth_timeout = sip->registrar.expires - 300;
5427 } else {
5428 /* NTLM: we have to reauthenticate as our security token expires
5429 after eight hours (be five minutes early) */
5430 reauth_timeout = (8 * 3600) - 300;
5432 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5433 g_free(action_name);
5434 sip->reauthenticate_set = TRUE;
5437 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5439 epid = get_epid(sip);
5440 uuid = generateUUIDfromEPID(epid);
5441 g_free(epid);
5443 // There can be multiple Contact headers (one per location where the user is logged in) so
5444 // make sure to only get the one for this uuid
5445 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5446 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5447 if (valid_contact) {
5448 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5449 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5450 g_free(valid_contact);
5451 break;
5452 } else {
5453 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5456 g_free(uuid);
5458 g_free(sip->contact);
5459 if(gruu) {
5460 sip->contact = g_strdup_printf("<%s>", gruu);
5461 g_free(gruu);
5462 } else {
5463 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5464 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);
5466 sip->ocs2007 = FALSE;
5467 sip->batched_support = FALSE;
5469 while(hdr)
5471 elem = hdr->data;
5472 if (sipe_strcase_equal(elem->name, "Supported")) {
5473 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5474 /* We interpret this as OCS2007+ indicator */
5475 sip->ocs2007 = TRUE;
5476 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
5478 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5479 sip->batched_support = TRUE;
5480 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
5483 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5484 gchar **caps = g_strsplit(elem->value,",",0);
5485 i = 0;
5486 while (caps[i]) {
5487 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5488 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
5489 i++;
5491 g_strfreev(caps);
5493 hdr = g_slist_next(hdr);
5496 /* rejoin open chats to be able to use them by continue to send messages */
5497 purple_conversation_foreach(sipe_rejoin_chat);
5499 /* subscriptions */
5500 if (!sip->subscribed) { //do it just once, not every re-register
5502 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5503 (GCompareFunc)g_ascii_strcasecmp)) {
5504 sipe_subscribe_roaming_contacts(sip);
5507 /* For 2007+ it does not make sence to subscribe to:
5508 * vnd-microsoft-roaming-ACL
5509 * vnd-microsoft-provisioning (not v2)
5510 * presence.wpending
5511 * These are for backward compatibility.
5513 if (sip->ocs2007)
5515 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5516 (GCompareFunc)g_ascii_strcasecmp)) {
5517 sipe_subscribe_roaming_self(sip);
5519 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5520 (GCompareFunc)g_ascii_strcasecmp)) {
5521 sipe_subscribe_roaming_provisioning_v2(sip);
5524 /* For 2005- servers */
5525 else
5527 //sipe_options_request(sip, sip->sipdomain);
5529 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5530 (GCompareFunc)g_ascii_strcasecmp)) {
5531 sipe_subscribe_roaming_acl(sip);
5533 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5534 (GCompareFunc)g_ascii_strcasecmp)) {
5535 sipe_subscribe_roaming_provisioning(sip);
5537 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5538 (GCompareFunc)g_ascii_strcasecmp)) {
5539 sipe_subscribe_presence_wpending(sip, msg);
5542 /* For 2007+ we publish our initial statuses and calendar data only after
5543 * received our existing publications in sipe_process_roaming_self()
5544 * Only in this case we know versions of current publications made
5545 * on our behalf.
5547 /* For 2005- we publish our initial statuses only after
5548 * received our existing UserInfo data in response to
5549 * self subscription.
5550 * Only in this case we won't override existing UserInfo data
5551 * set earlier or by other client on our behalf.
5555 sip->subscribed = TRUE;
5558 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5559 "timeout=", ";", NULL);
5560 if (timeout != NULL) {
5561 sscanf(timeout, "%u", &sip->keepalive_timeout);
5562 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5563 sip->keepalive_timeout);
5564 g_free(timeout);
5567 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
5569 break;
5570 case 301:
5572 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5574 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5575 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5576 gchar **tmp;
5577 gchar *hostname;
5578 int port = 0;
5579 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5580 int i = 1;
5582 tmp = g_strsplit(parts[0], ":", 0);
5583 hostname = g_strdup(tmp[0]);
5584 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5585 g_strfreev(tmp);
5587 while (parts[i]) {
5588 tmp = g_strsplit(parts[i], "=", 0);
5589 if (tmp[1]) {
5590 if (g_strcasecmp("transport", tmp[0]) == 0) {
5591 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5592 transport = SIPE_TRANSPORT_TCP;
5593 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5594 transport = SIPE_TRANSPORT_UDP;
5598 g_strfreev(tmp);
5599 i++;
5601 g_strfreev(parts);
5603 /* Close old connection */
5604 sipe_connection_cleanup(sip);
5606 /* Create new connection */
5607 sip->transport = transport;
5608 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5609 hostname, port, TRANSPORT_DESCRIPTOR);
5610 create_connection(sip, hostname, port);
5612 g_free(redirect);
5614 break;
5615 case 401:
5616 if (sip->registerstatus != 2) {
5617 const char *auth_scheme;
5618 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
5619 if (sip->registrar.retries > 3) {
5620 sip->gc->wants_to_die = TRUE;
5621 purple_connection_error(sip->gc, _("Authentication failed"));
5622 return TRUE;
5625 auth_scheme = sipe_get_auth_scheme_name(sip);
5626 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5628 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp ? tmp : "");
5629 if (!tmp) {
5630 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5631 sip->gc->wants_to_die = TRUE;
5632 purple_connection_error(sip->gc, tmp2);
5633 g_free(tmp2);
5634 return TRUE;
5636 fill_auth(tmp, &sip->registrar);
5637 sip->registerstatus = 2;
5638 if (sip->account->disconnecting) {
5639 do_register_exp(sip, 0);
5640 } else {
5641 do_register(sip);
5644 break;
5645 case 403:
5647 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5648 gchar **reason = NULL;
5649 gchar *warning;
5650 if (diagnostics != NULL) {
5651 /* Example header:
5652 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5654 reason = g_strsplit(diagnostics, "\"", 0);
5656 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5657 (reason && reason[1]) ? reason[1] : _("no reason given"));
5658 g_strfreev(reason);
5660 sip->gc->wants_to_die = TRUE;
5661 purple_connection_error(sip->gc, warning);
5662 g_free(warning);
5663 return TRUE;
5665 break;
5666 case 404:
5668 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5669 gchar *reason = NULL;
5670 gchar *warning;
5671 if (diagnostics != NULL) {
5672 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5674 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5675 diagnostics ? (reason ? reason : _("no reason given")) :
5676 _("SIP is either not enabled for the destination URI or it does not exist"));
5677 g_free(reason);
5679 sip->gc->wants_to_die = TRUE;
5680 purple_connection_error(sip->gc, warning);
5681 g_free(warning);
5682 return TRUE;
5684 break;
5685 case 503:
5686 case 504: /* Server time-out */
5688 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5689 gchar *reason = NULL;
5690 gchar *warning;
5691 if (diagnostics != NULL) {
5692 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5694 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5695 g_free(reason);
5697 sip->gc->wants_to_die = TRUE;
5698 purple_connection_error(sip->gc, warning);
5699 g_free(warning);
5700 return TRUE;
5702 break;
5704 return TRUE;
5708 * Returns 2005-style activity and Availability.
5710 * @param status Sipe statis id.
5712 static void
5713 sipe_get_act_avail_by_status_2005(const char *status,
5714 int *activity,
5715 int *availability)
5717 int avail = 300; /* online */
5718 int act = 400; /* Available */
5720 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5721 act = 100;
5722 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5723 // act = 150;
5724 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5725 act = 300;
5726 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5727 act = 400;
5728 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5729 // act = 500;
5730 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5731 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5732 act = 600;
5733 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5734 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5735 avail = 0; /* offline */
5736 act = 100;
5737 } else {
5738 act = 400; /* Available */
5741 if (activity) *activity = act;
5742 if (availability) *availability = avail;
5746 * [MS-SIP] 2.2.1
5748 * @param activity 2005 aggregated activity. Ex.: 600
5749 * @param availablity 2005 aggregated availablity. Ex.: 300
5751 static const char *
5752 sipe_get_status_by_act_avail_2005(const int activity,
5753 const int availablity,
5754 char **activity_desc)
5756 const char *status_id = NULL;
5757 const char *act = NULL;
5759 if (activity < 150) {
5760 status_id = SIPE_STATUS_ID_AWAY;
5761 } else if (activity < 200) {
5762 //status_id = SIPE_STATUS_ID_LUNCH;
5763 status_id = SIPE_STATUS_ID_AWAY;
5764 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5765 } else if (activity < 300) {
5766 //status_id = SIPE_STATUS_ID_IDLE;
5767 status_id = SIPE_STATUS_ID_AWAY;
5768 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5769 } else if (activity < 400) {
5770 status_id = SIPE_STATUS_ID_BRB;
5771 } else if (activity < 500) {
5772 status_id = SIPE_STATUS_ID_AVAILABLE;
5773 } else if (activity < 600) {
5774 //status_id = SIPE_STATUS_ID_ON_PHONE;
5775 status_id = SIPE_STATUS_ID_BUSY;
5776 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5777 } else if (activity < 700) {
5778 status_id = SIPE_STATUS_ID_BUSY;
5779 } else if (activity < 800) {
5780 status_id = SIPE_STATUS_ID_AWAY;
5781 } else {
5782 status_id = SIPE_STATUS_ID_AVAILABLE;
5785 if (availablity < 100)
5786 status_id = SIPE_STATUS_ID_OFFLINE;
5788 if (activity_desc && act) {
5789 g_free(*activity_desc);
5790 *activity_desc = g_strdup(act);
5793 return status_id;
5797 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5799 static const char*
5800 sipe_get_status_by_availability(int avail,
5801 char** activity_desc)
5803 const char *status;
5804 const char *act = NULL;
5806 if (avail < 3000) {
5807 status = SIPE_STATUS_ID_OFFLINE;
5808 } else if (avail < 4500) {
5809 status = SIPE_STATUS_ID_AVAILABLE;
5810 } else if (avail < 6000) {
5811 //status = SIPE_STATUS_ID_IDLE;
5812 status = SIPE_STATUS_ID_AVAILABLE;
5813 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5814 } else if (avail < 7500) {
5815 status = SIPE_STATUS_ID_BUSY;
5816 } else if (avail < 9000) {
5817 //status = SIPE_STATUS_ID_BUSYIDLE;
5818 status = SIPE_STATUS_ID_BUSY;
5819 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5820 } else if (avail < 12000) {
5821 status = SIPE_STATUS_ID_DND;
5822 } else if (avail < 15000) {
5823 status = SIPE_STATUS_ID_BRB;
5824 } else if (avail < 18000) {
5825 status = SIPE_STATUS_ID_AWAY;
5826 } else {
5827 status = SIPE_STATUS_ID_OFFLINE;
5830 if (activity_desc && act) {
5831 g_free(*activity_desc);
5832 *activity_desc = g_strdup(act);
5835 return status;
5839 * Returns 2007-style availability value
5841 * @param sipe_status_id (in)
5842 * @param activity_token (out) Must be g_free()'d after use if consumed.
5844 static int
5845 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5847 int availability;
5848 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5850 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5851 availability = 15500;
5852 if (!activity_token || !(*activity_token)) {
5853 activity = SIPE_ACTIVITY_AWAY;
5855 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5856 availability = 12500;
5857 activity = SIPE_ACTIVITY_BRB;
5858 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5859 availability = 9500;
5860 activity = SIPE_ACTIVITY_DND;
5861 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5862 availability = 6500;
5863 if (!activity_token || !(*activity_token)) {
5864 activity = SIPE_ACTIVITY_BUSY;
5866 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5867 availability = 3500;
5868 activity = SIPE_ACTIVITY_ONLINE;
5869 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5870 availability = 0;
5871 } else {
5872 // Offline or invisible
5873 availability = 18500;
5874 activity = SIPE_ACTIVITY_OFFLINE;
5877 if (activity_token) {
5878 *activity_token = g_strdup(sipe_activity_map[activity].token);
5880 return availability;
5883 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5885 const char *uri;
5886 sipe_xml *xn_categories;
5887 const sipe_xml *xn_category;
5888 const char *status = NULL;
5889 gboolean do_update_status = FALSE;
5890 gboolean has_note_cleaned = FALSE;
5891 gboolean has_free_busy_cleaned = FALSE;
5893 xn_categories = sipe_xml_parse(data, len);
5894 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
5896 for (xn_category = sipe_xml_child(xn_categories, "category");
5897 xn_category ;
5898 xn_category = sipe_xml_twin(xn_category) )
5900 const sipe_xml *xn_node;
5901 const char *tmp;
5902 const char *attrVar = sipe_xml_attribute(xn_category, "name");
5903 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
5904 sipe_utils_str_to_time(tmp) : 0;
5906 /* contactCard */
5907 if (sipe_strequal(attrVar, "contactCard"))
5909 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
5911 if (card) {
5912 const sipe_xml *node;
5913 /* identity - Display Name and email */
5914 node = sipe_xml_child(card, "identity");
5915 if (node) {
5916 char* display_name = sipe_xml_data(
5917 sipe_xml_child(node, "name/displayName"));
5918 char* email = sipe_xml_data(
5919 sipe_xml_child(node, "email"));
5921 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5922 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5924 g_free(display_name);
5925 g_free(email);
5927 /* company */
5928 node = sipe_xml_child(card, "company");
5929 if (node) {
5930 char* company = sipe_xml_data(node);
5931 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5932 g_free(company);
5934 /* department */
5935 node = sipe_xml_child(card, "department");
5936 if (node) {
5937 char* department = sipe_xml_data(node);
5938 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5939 g_free(department);
5941 /* title */
5942 node = sipe_xml_child(card, "title");
5943 if (node) {
5944 char* title = sipe_xml_data(node);
5945 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5946 g_free(title);
5948 /* office */
5949 node = sipe_xml_child(card, "office");
5950 if (node) {
5951 char* office = sipe_xml_data(node);
5952 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5953 g_free(office);
5955 /* site (url) */
5956 node = sipe_xml_child(card, "url");
5957 if (node) {
5958 char* site = sipe_xml_data(node);
5959 sipe_update_user_info(sip, uri, SITE_PROP, site);
5960 g_free(site);
5962 /* phone */
5963 for (node = sipe_xml_child(card, "phone");
5964 node;
5965 node = sipe_xml_twin(node))
5967 const char *phone_type = sipe_xml_attribute(node, "type");
5968 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
5969 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
5971 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5973 g_free(phone);
5974 g_free(phone_display_string);
5976 /* address */
5977 for (node = sipe_xml_child(card, "address");
5978 node;
5979 node = sipe_xml_twin(node))
5981 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
5982 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
5983 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
5984 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
5985 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
5986 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
5988 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5989 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5990 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5991 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5992 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5994 g_free(street);
5995 g_free(city);
5996 g_free(state);
5997 g_free(zipcode);
5998 g_free(country_code);
6000 break;
6005 /* note */
6006 else if (sipe_strequal(attrVar, "note"))
6008 if (uri) {
6009 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6011 if (!has_note_cleaned) {
6012 has_note_cleaned = TRUE;
6014 g_free(sbuddy->note);
6015 sbuddy->note = NULL;
6016 sbuddy->is_oof_note = FALSE;
6017 sbuddy->note_since = publish_time;
6019 do_update_status = TRUE;
6021 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6022 /* clean up in case no 'note' element is supplied
6023 * which indicate note removal in client
6025 g_free(sbuddy->note);
6026 sbuddy->note = NULL;
6027 sbuddy->is_oof_note = FALSE;
6028 sbuddy->note_since = publish_time;
6030 xn_node = sipe_xml_child(xn_category, "note/body");
6031 if (xn_node) {
6032 char *tmp;
6033 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6034 g_free(tmp);
6035 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6036 sbuddy->note_since = publish_time;
6038 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
6039 uri, sbuddy->note ? sbuddy->note : "");
6041 /* to trigger UI refresh in case no status info is supplied in this update */
6042 do_update_status = TRUE;
6046 /* state */
6047 else if(sipe_strequal(attrVar, "state"))
6049 char *tmp;
6050 int availability;
6051 const sipe_xml *xn_availability;
6052 const sipe_xml *xn_activity;
6053 const sipe_xml *xn_meeting_subject;
6054 const sipe_xml *xn_meeting_location;
6055 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6057 xn_node = sipe_xml_child(xn_category, "state");
6058 if (!xn_node) continue;
6059 xn_availability = sipe_xml_child(xn_node, "availability");
6060 if (!xn_availability) continue;
6061 xn_activity = sipe_xml_child(xn_node, "activity");
6062 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6063 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6065 tmp = sipe_xml_data(xn_availability);
6066 availability = atoi(tmp);
6067 g_free(tmp);
6069 /* activity, meeting_subject, meeting_location */
6070 if (sbuddy) {
6071 char *tmp = NULL;
6073 /* activity */
6074 g_free(sbuddy->activity);
6075 sbuddy->activity = NULL;
6076 if (xn_activity) {
6077 const char *token = sipe_xml_attribute(xn_activity, "token");
6078 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6080 /* from token */
6081 if (!is_empty(token)) {
6082 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6084 /* from custom element */
6085 if (xn_custom) {
6086 char *custom = sipe_xml_data(xn_custom);
6088 if (!is_empty(custom)) {
6089 sbuddy->activity = custom;
6090 custom = NULL;
6092 g_free(custom);
6095 /* meeting_subject */
6096 g_free(sbuddy->meeting_subject);
6097 sbuddy->meeting_subject = NULL;
6098 if (xn_meeting_subject) {
6099 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6101 if (!is_empty(meeting_subject)) {
6102 sbuddy->meeting_subject = meeting_subject;
6103 meeting_subject = NULL;
6105 g_free(meeting_subject);
6107 /* meeting_location */
6108 g_free(sbuddy->meeting_location);
6109 sbuddy->meeting_location = NULL;
6110 if (xn_meeting_location) {
6111 char *meeting_location = sipe_xml_data(xn_meeting_location);
6113 if (!is_empty(meeting_location)) {
6114 sbuddy->meeting_location = meeting_location;
6115 meeting_location = NULL;
6117 g_free(meeting_location);
6120 status = sipe_get_status_by_availability(availability, &tmp);
6121 if (sbuddy->activity && tmp) {
6122 char *tmp2 = sbuddy->activity;
6124 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6125 g_free(tmp);
6126 g_free(tmp2);
6127 } else if (tmp) {
6128 sbuddy->activity = tmp;
6132 do_update_status = TRUE;
6134 /* calendarData */
6135 else if(sipe_strequal(attrVar, "calendarData"))
6137 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6138 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6139 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6141 if (sbuddy && xn_free_busy) {
6142 if (!has_free_busy_cleaned) {
6143 has_free_busy_cleaned = TRUE;
6145 g_free(sbuddy->cal_start_time);
6146 sbuddy->cal_start_time = NULL;
6148 g_free(sbuddy->cal_free_busy_base64);
6149 sbuddy->cal_free_busy_base64 = NULL;
6151 g_free(sbuddy->cal_free_busy);
6152 sbuddy->cal_free_busy = NULL;
6154 sbuddy->cal_free_busy_published = publish_time;
6157 if (publish_time >= sbuddy->cal_free_busy_published) {
6158 g_free(sbuddy->cal_start_time);
6159 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6161 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6162 15 : 0;
6164 g_free(sbuddy->cal_free_busy_base64);
6165 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6167 g_free(sbuddy->cal_free_busy);
6168 sbuddy->cal_free_busy = NULL;
6170 sbuddy->cal_free_busy_published = publish_time;
6172 purple_debug_info("sipe", "process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s\n", sbuddy->cal_start_time, sbuddy->cal_granularity, sbuddy->cal_free_busy_base64);
6176 if (sbuddy && xn_working_hours) {
6177 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6182 if (do_update_status) {
6183 if (!status) { /* no status category in this update, using contact's current status */
6184 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6185 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6186 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6187 status = purple_status_get_id(pstatus);
6190 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
6191 sipe_got_user_status(sip, uri, status);
6194 sipe_xml_free(xn_categories);
6197 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6199 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6200 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
6201 payload->host = g_strdup(host);
6202 payload->buddies = server;
6203 sipe_subscribe_presence_batched_routed(sip, payload);
6204 sipe_subscribe_presence_batched_routed_free(payload);
6207 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6209 xmlnode *xn_list;
6210 xmlnode *xn_resource;
6211 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6212 g_free, NULL);
6213 GSList *server;
6214 gchar *host;
6216 xn_list = xmlnode_from_str(data, len);
6218 for (xn_resource = xmlnode_get_child(xn_list, "resource");
6219 xn_resource;
6220 xn_resource = xmlnode_get_next_twin(xn_resource) )
6222 const char *uri, *state;
6223 xmlnode *xn_instance;
6225 xn_instance = xmlnode_get_child(xn_resource, "instance");
6226 if (!xn_instance) continue;
6228 uri = xmlnode_get_attrib(xn_resource, "uri");
6229 state = xmlnode_get_attrib(xn_instance, "state");
6230 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
6232 if (strstr(state, "resubscribe")) {
6233 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
6235 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6236 gchar *user = g_strdup(uri);
6237 host = g_strdup(poolFqdn);
6238 server = g_hash_table_lookup(servers, host);
6239 server = g_slist_append(server, user);
6240 g_hash_table_insert(servers, host, server);
6241 } else {
6242 sipe_subscribe_presence_single(sip, (void *) uri);
6247 /* Send out any deferred poolFqdn subscriptions */
6248 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6249 g_hash_table_destroy(servers);
6251 xmlnode_free(xn_list);
6254 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6256 gchar *uri;
6257 gchar *getbasic;
6258 gchar *activity = NULL;
6259 xmlnode *pidf;
6260 xmlnode *basicstatus = NULL, *tuple, *status;
6261 gboolean isonline = FALSE;
6262 xmlnode *display_name_node;
6264 pidf = xmlnode_from_str(data, len);
6265 if (!pidf) {
6266 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data);
6267 return;
6270 if ((tuple = xmlnode_get_child(pidf, "tuple")))
6272 if ((status = xmlnode_get_child(tuple, "status"))) {
6273 basicstatus = xmlnode_get_child(status, "basic");
6277 if (!basicstatus) {
6278 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6279 xmlnode_free(pidf);
6280 return;
6283 getbasic = xmlnode_get_data(basicstatus);
6284 if (!getbasic) {
6285 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6286 xmlnode_free(pidf);
6287 return;
6290 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic);
6291 if (strstr(getbasic, "open")) {
6292 isonline = TRUE;
6294 g_free(getbasic);
6296 uri = sip_uri(xmlnode_get_attrib(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6298 display_name_node = xmlnode_get_child(pidf, "display-name");
6299 if (display_name_node) {
6300 char * display_name = xmlnode_get_data(display_name_node);
6302 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6303 g_free(display_name);
6306 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
6307 if ((status = xmlnode_get_child(tuple, "status"))) {
6308 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
6309 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
6310 activity = xmlnode_get_data(basicstatus);
6311 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity);
6317 if (isonline) {
6318 const gchar * status_id = NULL;
6319 if (activity) {
6320 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6321 status_id = SIPE_STATUS_ID_BUSY;
6322 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6323 status_id = SIPE_STATUS_ID_AWAY;
6327 if (!status_id) {
6328 status_id = SIPE_STATUS_ID_AVAILABLE;
6331 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id);
6332 sipe_got_user_status(sip, uri, status_id);
6333 } else {
6334 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6337 g_free(activity);
6338 g_free(uri);
6339 xmlnode_free(pidf);
6342 /** 2005 */
6343 static void
6344 sipe_user_info_has_updated(struct sipe_account_data *sip,
6345 xmlnode *xn_userinfo)
6347 xmlnode *xn_states;
6349 g_free(sip->user_states);
6350 sip->user_states = NULL;
6351 if ((xn_states = xmlnode_get_child(xn_userinfo, "states")) != NULL) {
6352 sip->user_states = xmlnode_to_str(xn_states, NULL);
6353 /* this is a hack-around to remove added newline after inner element,
6354 * state in this case, where it shouldn't be.
6355 * After several use of xmlnode_to_str, amount of added newlines
6356 * grows significantly.
6358 purple_str_strip_char(sip->user_states, '\n');
6359 //purple_str_strip_char(sip->user_states, '\r');
6362 /* Publish initial state if not yet.
6363 * Assuming this happens on initial responce to self subscription
6364 * so we've already updated our UserInfo.
6366 if (!sip->initial_state_published) {
6367 send_presence_soap(sip, FALSE);
6368 /* dalayed run */
6369 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6373 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6375 char *activity = NULL;
6376 const char *epid;
6377 const char *status_id = NULL;
6378 const char *name;
6379 char *uri;
6380 char *self_uri = sip_uri_self(sip);
6381 int avl;
6382 int act;
6383 const char *device_name = NULL;
6384 const char *cal_start_time = NULL;
6385 const char *cal_granularity = NULL;
6386 char *cal_free_busy_base64 = NULL;
6387 struct sipe_buddy *sbuddy;
6388 xmlnode *node;
6389 xmlnode *xn_presentity;
6390 xmlnode *xn_availability;
6391 xmlnode *xn_activity;
6392 xmlnode *xn_display_name;
6393 xmlnode *xn_email;
6394 xmlnode *xn_phone_number;
6395 xmlnode *xn_userinfo;
6396 xmlnode *xn_note;
6397 xmlnode *xn_oof;
6398 xmlnode *xn_state;
6399 xmlnode *xn_contact;
6400 char *note;
6401 char *free_activity;
6402 int user_avail;
6403 const char *user_avail_nil;
6404 int res_avail;
6405 time_t user_avail_since = 0;
6406 time_t activity_since = 0;
6408 /* fix for Reuters environment on Linux */
6409 if (data && strstr(data, "encoding=\"utf-16\"")) {
6410 char *tmp_data;
6411 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6412 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
6413 g_free(tmp_data);
6414 } else {
6415 xn_presentity = xmlnode_from_str(data, len);
6418 xn_availability = xmlnode_get_child(xn_presentity, "availability");
6419 xn_activity = xmlnode_get_child(xn_presentity, "activity");
6420 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
6421 xn_email = xmlnode_get_child(xn_presentity, "email");
6422 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
6423 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
6424 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
6425 xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL): NULL;
6426 user_avail = xn_state ? xmlnode_get_int_attrib(xn_state, "avail", 0) : 0;
6427 user_avail_since = xn_state ? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since")) : 0;
6428 user_avail_nil = xn_state ? xmlnode_get_attrib(xn_state, "nil") : NULL;
6429 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
6430 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
6431 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
6433 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6434 user_avail = 0;
6435 user_avail_since = 0;
6438 free_activity = NULL;
6440 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
6441 uri = sip_uri_from_name(name);
6442 avl = xmlnode_get_int_attrib(xn_availability, "aggregate", 0);
6443 epid = xmlnode_get_attrib(xn_availability, "epid");
6444 act = xmlnode_get_int_attrib(xn_activity, "aggregate", 0);
6446 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6447 res_avail = sipe_get_availability_by_status(status_id, NULL);
6448 if (user_avail > res_avail) {
6449 res_avail = user_avail;
6450 status_id = sipe_get_status_by_availability(user_avail, NULL);
6453 if (xn_display_name) {
6454 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
6455 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
6456 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
6457 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
6458 char *tel_uri = sip_to_tel_uri(phone_number);
6460 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6461 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6462 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6463 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6465 g_free(tel_uri);
6466 g_free(phone_label);
6467 g_free(phone_number);
6468 g_free(email);
6469 g_free(display_name);
6472 if (xn_contact) {
6473 /* tel */
6474 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
6476 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6477 const char *phone_type = xmlnode_get_attrib(node, "type");
6478 char* phone = xmlnode_get_data(node);
6480 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6482 g_free(phone);
6486 /* devicePresence */
6487 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
6488 xmlnode *xn_device_name;
6489 xmlnode *xn_calendar_info;
6490 xmlnode *xn_state;
6491 char *state;
6493 /* deviceName */
6494 if (sipe_strequal(xmlnode_get_attrib(node, "epid"), epid)) {
6495 xn_device_name = xmlnode_get_child(node, "deviceName");
6496 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
6499 /* calendarInfo */
6500 xn_calendar_info = xmlnode_get_child(node, "calendarInfo");
6501 if (xn_calendar_info) {
6502 const char *cal_start_time_tmp = xmlnode_get_attrib(xn_calendar_info, "startTime");
6504 if (cal_start_time) {
6505 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6506 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6508 if (cal_start_time_t_tmp > cal_start_time_t) {
6509 cal_start_time = cal_start_time_tmp;
6510 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6511 g_free(cal_free_busy_base64);
6512 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6514 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time, cal_granularity, cal_free_busy_base64);
6516 } else {
6517 cal_start_time = cal_start_time_tmp;
6518 cal_granularity = xmlnode_get_attrib(xn_calendar_info, "granularity");
6519 g_free(cal_free_busy_base64);
6520 cal_free_busy_base64 = xmlnode_get_data(xn_calendar_info);
6522 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time, cal_granularity, cal_free_busy_base64);
6526 /* state */
6527 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
6528 if (xn_state) {
6529 int dev_avail = xmlnode_get_int_attrib(xn_state, "avail", 0);
6530 time_t dev_avail_since = sipe_utils_str_to_time(xmlnode_get_attrib(xn_state, "since"));
6532 state = xmlnode_get_data(xn_state);
6533 if (dev_avail_since > user_avail_since &&
6534 dev_avail >= res_avail)
6536 res_avail = dev_avail;
6537 if (!is_empty(state))
6539 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6540 g_free(activity);
6541 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6542 } else if (sipe_strequal(state, "presenting")) {
6543 g_free(activity);
6544 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6545 } else {
6546 activity = state;
6547 state = NULL;
6549 activity_since = dev_avail_since;
6551 status_id = sipe_get_status_by_availability(res_avail, &activity);
6553 g_free(state);
6557 /* oof */
6558 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6559 g_free(activity);
6560 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6561 activity_since = 0;
6564 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6565 if (sbuddy)
6567 g_free(sbuddy->activity);
6568 sbuddy->activity = activity;
6569 activity = NULL;
6571 sbuddy->activity_since = activity_since;
6573 sbuddy->user_avail = user_avail;
6574 sbuddy->user_avail_since = user_avail_since;
6576 g_free(sbuddy->note);
6577 sbuddy->note = NULL;
6578 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6580 sbuddy->is_oof_note = (xn_oof != NULL);
6582 g_free(sbuddy->device_name);
6583 sbuddy->device_name = NULL;
6584 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6586 if (!is_empty(cal_free_busy_base64)) {
6587 g_free(sbuddy->cal_start_time);
6588 sbuddy->cal_start_time = g_strdup(cal_start_time);
6590 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6592 g_free(sbuddy->cal_free_busy_base64);
6593 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6594 cal_free_busy_base64 = NULL;
6596 g_free(sbuddy->cal_free_busy);
6597 sbuddy->cal_free_busy = NULL;
6600 sbuddy->last_non_cal_status_id = status_id;
6601 g_free(sbuddy->last_non_cal_activity);
6602 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6604 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6605 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6607 sip->is_oof_note = sbuddy->is_oof_note;
6609 g_free(sip->note);
6610 sip->note = g_strdup(sbuddy->note);
6612 sip->note_since = time(NULL);
6615 g_free(sip->status);
6616 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6619 g_free(cal_free_busy_base64);
6620 g_free(activity);
6622 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
6623 sipe_got_user_status(sip, uri, status_id);
6625 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6626 sipe_user_info_has_updated(sip, xn_userinfo);
6629 g_free(note);
6630 xmlnode_free(xn_presentity);
6631 g_free(uri);
6632 g_free(self_uri);
6635 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6637 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6639 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
6641 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
6642 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
6644 const char *content = msg->body;
6645 unsigned length = msg->bodylen;
6646 PurpleMimeDocument *mime = NULL;
6648 if (strstr(ctype, "multipart"))
6650 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6651 const char *content_type;
6652 GList* parts;
6653 mime = purple_mime_document_parse(doc);
6654 parts = purple_mime_document_get_parts(mime);
6655 while(parts) {
6656 content = purple_mime_part_get_data(parts->data);
6657 length = purple_mime_part_get_length(parts->data);
6658 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
6659 if(content_type && strstr(content_type,"application/rlmi+xml"))
6661 process_incoming_notify_rlmi_resub(sip, content, length);
6663 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
6665 process_incoming_notify_msrtc(sip, content, length);
6667 else
6669 process_incoming_notify_rlmi(sip, content, length);
6671 parts = parts->next;
6673 g_free(doc);
6675 if (mime)
6677 purple_mime_document_free(mime);
6680 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6682 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6684 else if(strstr(ctype, "application/rlmi+xml"))
6686 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6689 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6691 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6693 else
6695 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6699 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6701 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6702 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6704 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
6706 if (ctype &&
6707 strstr(ctype, "multipart") &&
6708 (strstr(ctype, "application/rlmi+xml") ||
6709 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6710 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
6711 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
6712 GList *parts = purple_mime_document_get_parts(mime);
6713 GSList *buddies = NULL;
6714 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6716 while (parts) {
6717 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
6718 purple_mime_part_get_length(parts->data));
6720 if (xml && !sipe_strequal(xml->name, "list")) {
6721 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
6723 buddies = g_slist_append(buddies, uri);
6725 xmlnode_free(xml);
6727 parts = parts->next;
6729 g_free(doc);
6730 if (mime) purple_mime_document_free(mime);
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 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
6740 } else {
6741 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
6742 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", 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 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6763 event ? event : "",
6764 tmp = fix_newlines(msg->body));
6765 g_free(tmp);
6766 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", 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 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
6823 g_free(who);
6825 if (g_hash_table_lookup(sip->subscriptions, key)) {
6826 g_hash_table_remove(sip->subscriptions, key);
6827 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", 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 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", 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 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", 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 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6887 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now)));
6889 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6891 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", 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 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
6920 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6923 purple_debug_info("sipe", "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 = purple_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 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", 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 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", 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 struct sipe_publication *publication =
7101 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
7103 purple_debug_info("sipe", "key is %s\n", key);
7105 if (publication) {
7106 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
7107 key, curVersion, publication->version);
7108 /* updating publication's version to the correct one */
7109 publication->version = atoi(curVersion);
7111 g_free(key);
7114 xmlnode_free(xml);
7115 g_hash_table_destroy(faults);
7117 /* rebublishing with right versions */
7118 if (has_device_publication) {
7119 send_publish_category_initial(sip);
7120 } else {
7121 send_presence_status(sip);
7124 return TRUE;
7128 * Returns 'device' XML part for publication.
7129 * Must be g_free'd after use.
7131 static gchar *
7132 sipe_publish_get_category_device(struct sipe_account_data *sip)
7134 gchar *uri;
7135 gchar *doc;
7136 gchar *epid = get_epid(sip);
7137 gchar *uuid = generateUUIDfromEPID(epid);
7138 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7139 /* key is <category><instance><container> */
7140 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7141 struct sipe_publication *publication =
7142 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7144 g_free(key);
7145 g_free(epid);
7147 uri = sip_uri_self(sip);
7148 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7149 device_instance,
7150 publication ? publication->version : 0,
7151 uuid,
7152 uri,
7153 "00:00:00+01:00", /* @TODO make timezone real*/
7154 g_get_host_name()
7157 g_free(uri);
7158 g_free(uuid);
7160 return doc;
7164 * A service method - use
7165 * - send_publish_get_category_state_machine and
7166 * - send_publish_get_category_state_user instead.
7167 * Must be g_free'd after use.
7169 static gchar *
7170 sipe_publish_get_category_state(struct sipe_account_data *sip,
7171 gboolean is_user_state)
7173 int availability = sipe_get_availability_by_status(sip->status, NULL);
7174 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7175 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7176 /* key is <category><instance><container> */
7177 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7178 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7179 struct sipe_publication *publication_2 =
7180 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7181 struct sipe_publication *publication_3 =
7182 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7184 g_free(key_2);
7185 g_free(key_3);
7187 if (publication_2 && (publication_2->availability == availability))
7189 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
7190 return NULL; /* nothing to update */
7193 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7194 instance,
7195 publication_2 ? publication_2->version : 0,
7196 availability,
7197 instance,
7198 publication_3 ? publication_3->version : 0,
7199 availability);
7203 * Only Busy and OOF calendar event are published.
7204 * Different instances are used for that.
7206 * Must be g_free'd after use.
7208 static gchar *
7209 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7210 struct sipe_cal_event *event,
7211 const char *uri,
7212 int cal_satus)
7214 gchar *start_time_str;
7215 int availability = 0;
7216 gchar *res;
7217 gchar *tmp = NULL;
7218 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7219 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7220 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7222 /* key is <category><instance><container> */
7223 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7224 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7225 struct sipe_publication *publication_2 =
7226 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7227 struct sipe_publication *publication_3 =
7228 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7230 g_free(key_2);
7231 g_free(key_3);
7233 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7234 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7235 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus);
7236 return NULL;
7239 if (event &&
7240 publication_3 &&
7241 (publication_3->availability == availability) &&
7242 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7244 g_free(tmp);
7245 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7246 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus);
7247 return NULL; /* nothing to update */
7249 g_free(tmp);
7251 if (event &&
7252 (event->cal_status == SIPE_CAL_BUSY ||
7253 event->cal_status == SIPE_CAL_OOF))
7255 gchar *availability_xml_str = NULL;
7256 gchar *activity_xml_str = NULL;
7258 if (event->cal_status == SIPE_CAL_BUSY) {
7259 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7262 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7263 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7264 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7265 "minAvailability=\"6500\"",
7266 "maxAvailability=\"8999\"");
7267 } else if (event->cal_status == SIPE_CAL_OOF) {
7268 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7269 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7270 "minAvailability=\"12000\"",
7271 "");
7273 start_time_str = sipe_utils_time_to_str(event->start_time);
7275 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7276 instance,
7277 publication_2 ? publication_2->version : 0,
7278 uri,
7279 start_time_str,
7280 availability_xml_str ? availability_xml_str : "",
7281 activity_xml_str ? activity_xml_str : "",
7282 event->subject ? event->subject : "",
7283 event->location ? event->location : "",
7285 instance,
7286 publication_3 ? publication_3->version : 0,
7287 uri,
7288 start_time_str,
7289 availability_xml_str ? availability_xml_str : "",
7290 activity_xml_str ? activity_xml_str : "",
7291 event->subject ? event->subject : "",
7292 event->location ? event->location : ""
7294 g_free(start_time_str);
7295 g_free(availability_xml_str);
7296 g_free(activity_xml_str);
7299 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7301 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7302 instance,
7303 publication_2 ? publication_2->version : 0,
7305 instance,
7306 publication_3 ? publication_3->version : 0
7310 return res;
7314 * Returns 'machineState' XML part for publication.
7315 * Must be g_free'd after use.
7317 static gchar *
7318 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7320 return sipe_publish_get_category_state(sip, FALSE);
7324 * Returns 'userState' XML part for publication.
7325 * Must be g_free'd after use.
7327 static gchar *
7328 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7330 return sipe_publish_get_category_state(sip, TRUE);
7334 * Returns 'note' XML part for publication.
7335 * Must be g_free'd after use.
7337 * Protocol format for Note is plain text.
7339 * @param note a note in Sipe internal HTML format
7340 * @param note_type either personal or OOF
7342 static gchar *
7343 sipe_publish_get_category_note(struct sipe_account_data *sip,
7344 const char *note, /* html */
7345 const char *note_type,
7346 time_t note_start,
7347 time_t note_end)
7349 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7350 /* key is <category><instance><container> */
7351 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7352 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7353 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7355 struct sipe_publication *publication_note_200 =
7356 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7357 struct sipe_publication *publication_note_300 =
7358 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7359 struct sipe_publication *publication_note_400 =
7360 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7362 char *tmp = note ? purple_markup_strip_html(note) : NULL;
7363 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7364 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7365 char *res, *tmp1, *tmp2, *tmp3;
7366 char *start_time_attr;
7367 char *end_time_attr;
7369 g_free(tmp);
7370 tmp = NULL;
7371 g_free(key_note_200);
7372 g_free(key_note_300);
7373 g_free(key_note_400);
7375 /* we even need to republish empty note */
7376 if (sipe_strequal(n1, n2))
7378 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7379 g_free(n1);
7380 return NULL; /* nothing to update */
7383 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7384 g_free(tmp);
7385 tmp = NULL;
7386 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7387 g_free(tmp);
7389 if (n1) {
7390 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7391 instance,
7392 200,
7393 publication_note_200 ? publication_note_200->version : 0,
7394 note_type,
7395 start_time_attr ? start_time_attr : "",
7396 end_time_attr ? end_time_attr : "",
7397 n1);
7399 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7400 instance,
7401 300,
7402 publication_note_300 ? publication_note_300->version : 0,
7403 note_type,
7404 start_time_attr ? start_time_attr : "",
7405 end_time_attr ? end_time_attr : "",
7406 n1);
7408 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7409 instance,
7410 400,
7411 publication_note_400 ? publication_note_400->version : 0,
7412 note_type,
7413 start_time_attr ? start_time_attr : "",
7414 end_time_attr ? end_time_attr : "",
7415 n1);
7416 } else {
7417 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7418 "note",
7419 instance,
7420 200,
7421 publication_note_200 ? publication_note_200->version : 0,
7422 "static");
7423 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7424 "note",
7425 instance,
7426 300,
7427 publication_note_200 ? publication_note_200->version : 0,
7428 "static");
7429 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7430 "note",
7431 instance,
7432 400,
7433 publication_note_200 ? publication_note_200->version : 0,
7434 "static");
7436 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7438 g_free(start_time_attr);
7439 g_free(end_time_attr);
7440 g_free(tmp1);
7441 g_free(tmp2);
7442 g_free(tmp3);
7443 g_free(n1);
7445 return res;
7449 * Returns 'calendarData' XML part with WorkingHours for publication.
7450 * Must be g_free'd after use.
7452 static gchar *
7453 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7455 struct sipe_ews* ews = sip->ews;
7457 /* key is <category><instance><container> */
7458 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7459 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7460 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7461 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7462 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7463 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7465 struct sipe_publication *publication_cal_1 =
7466 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7467 struct sipe_publication *publication_cal_100 =
7468 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7469 struct sipe_publication *publication_cal_200 =
7470 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7471 struct sipe_publication *publication_cal_300 =
7472 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7473 struct sipe_publication *publication_cal_400 =
7474 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7475 struct sipe_publication *publication_cal_32000 =
7476 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7478 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7479 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7481 g_free(key_cal_1);
7482 g_free(key_cal_100);
7483 g_free(key_cal_200);
7484 g_free(key_cal_300);
7485 g_free(key_cal_400);
7486 g_free(key_cal_32000);
7488 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7489 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7490 return NULL;
7493 if (sipe_strequal(n1, n2))
7495 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7496 return NULL; /* nothing to update */
7499 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7500 /* 1 */
7501 publication_cal_1 ? publication_cal_1->version : 0,
7502 ews->email,
7503 ews->working_hours_xml_str,
7504 /* 100 - Public */
7505 publication_cal_100 ? publication_cal_100->version : 0,
7506 /* 200 - Company */
7507 publication_cal_200 ? publication_cal_200->version : 0,
7508 ews->email,
7509 ews->working_hours_xml_str,
7510 /* 300 - Team */
7511 publication_cal_300 ? publication_cal_300->version : 0,
7512 ews->email,
7513 ews->working_hours_xml_str,
7514 /* 400 - Personal */
7515 publication_cal_400 ? publication_cal_400->version : 0,
7516 ews->email,
7517 ews->working_hours_xml_str,
7518 /* 32000 - Blocked */
7519 publication_cal_32000 ? publication_cal_32000->version : 0
7524 * Returns 'calendarData' XML part with FreeBusy for publication.
7525 * Must be g_free'd after use.
7527 static gchar *
7528 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7530 struct sipe_ews* ews = sip->ews;
7531 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7532 char *fb_start_str;
7533 char *free_busy_base64;
7534 const char *st;
7535 const char *fb;
7536 char *res;
7538 /* key is <category><instance><container> */
7539 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7540 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7541 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7542 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7543 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7544 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7546 struct sipe_publication *publication_cal_1 =
7547 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7548 struct sipe_publication *publication_cal_100 =
7549 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7550 struct sipe_publication *publication_cal_200 =
7551 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7552 struct sipe_publication *publication_cal_300 =
7553 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7554 struct sipe_publication *publication_cal_400 =
7555 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7556 struct sipe_publication *publication_cal_32000 =
7557 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7559 g_free(key_cal_1);
7560 g_free(key_cal_100);
7561 g_free(key_cal_200);
7562 g_free(key_cal_300);
7563 g_free(key_cal_400);
7564 g_free(key_cal_32000);
7566 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7567 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7568 return NULL;
7571 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7572 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7574 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7575 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7577 /* we will rebuplish the same data to refresh publication time,
7578 * so if data from multiple sources, most recent will be choosen
7580 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7582 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7583 // g_free(fb_start_str);
7584 // g_free(free_busy_base64);
7585 // return NULL; /* nothing to update */
7588 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7589 /* 1 */
7590 cal_data_instance,
7591 publication_cal_1 ? publication_cal_1->version : 0,
7592 /* 100 - Public */
7593 cal_data_instance,
7594 publication_cal_100 ? publication_cal_100->version : 0,
7595 /* 200 - Company */
7596 cal_data_instance,
7597 publication_cal_200 ? publication_cal_200->version : 0,
7598 ews->email,
7599 fb_start_str,
7600 free_busy_base64,
7601 /* 300 - Team */
7602 cal_data_instance,
7603 publication_cal_300 ? publication_cal_300->version : 0,
7604 ews->email,
7605 fb_start_str,
7606 free_busy_base64,
7607 /* 400 - Personal */
7608 cal_data_instance,
7609 publication_cal_400 ? publication_cal_400->version : 0,
7610 ews->email,
7611 fb_start_str,
7612 free_busy_base64,
7613 /* 32000 - Blocked */
7614 cal_data_instance,
7615 publication_cal_32000 ? publication_cal_32000->version : 0
7618 g_free(fb_start_str);
7619 g_free(free_busy_base64);
7620 return res;
7623 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7625 gchar *uri;
7626 gchar *doc;
7627 gchar *tmp;
7628 gchar *hdr;
7630 uri = sip_uri_self(sip);
7631 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7632 uri,
7633 publications);
7635 tmp = get_contact(sip);
7636 hdr = g_strdup_printf("Contact: %s\r\n"
7637 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7639 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7641 g_free(tmp);
7642 g_free(hdr);
7643 g_free(uri);
7644 g_free(doc);
7647 static void
7648 send_publish_category_initial(struct sipe_account_data *sip)
7650 gchar *pub_device = sipe_publish_get_category_device(sip);
7651 gchar *pub_machine;
7652 gchar *publications;
7654 g_free(sip->status);
7655 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7657 pub_machine = sipe_publish_get_category_state_machine(sip);
7658 publications = g_strdup_printf("%s%s",
7659 pub_device,
7660 pub_machine ? pub_machine : "");
7661 g_free(pub_device);
7662 g_free(pub_machine);
7664 send_presence_publish(sip, publications);
7665 g_free(publications);
7668 static void
7669 send_presence_category_publish(struct sipe_account_data *sip)
7671 gchar *pub_state = sipe_is_user_state(sip) ?
7672 sipe_publish_get_category_state_user(sip) :
7673 sipe_publish_get_category_state_machine(sip);
7674 gchar *pub_note = sipe_publish_get_category_note(sip,
7675 sip->note,
7676 sip->is_oof_note ? "OOF" : "personal",
7679 gchar *publications;
7681 if (!pub_state && !pub_note) {
7682 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7683 return;
7686 publications = g_strdup_printf("%s%s",
7687 pub_state ? pub_state : "",
7688 pub_note ? pub_note : "");
7690 g_free(pub_state);
7691 g_free(pub_note);
7693 send_presence_publish(sip, publications);
7694 g_free(publications);
7698 * Publishes self status
7699 * based on own calendar information.
7701 * For 2007+
7703 void
7704 publish_calendar_status_self(struct sipe_account_data *sip)
7706 struct sipe_cal_event* event = NULL;
7707 gchar *pub_cal_working_hours = NULL;
7708 gchar *pub_cal_free_busy = NULL;
7709 gchar *pub_calendar = NULL;
7710 gchar *pub_calendar2 = NULL;
7711 gchar *pub_oof_note = NULL;
7712 const gchar *oof_note;
7713 time_t oof_start = 0;
7714 time_t oof_end = 0;
7716 if (!sip->ews) {
7717 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7718 return;
7721 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7722 if (sip->ews->cal_events) {
7723 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
7726 if (!event) {
7727 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7728 } else {
7729 char *desc = sipe_cal_event_describe(event);
7730 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7731 g_free(desc);
7734 /* Logic
7735 if OOF
7736 OOF publish, Busy clean
7737 ilse if Busy
7738 OOF clean, Busy publish
7739 else
7740 OOF clean, Busy clean
7742 if (event && event->cal_status == SIPE_CAL_OOF) {
7743 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
7744 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7745 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7746 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7747 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
7748 } else {
7749 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
7750 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
7753 oof_note = sipe_ews_get_oof_note(sip->ews);
7754 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
7755 oof_start = sip->ews->oof_start;
7756 oof_end = sip->ews->oof_end;
7758 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7760 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7761 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7763 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7764 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7765 } else {
7766 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7767 pub_cal_working_hours ? pub_cal_working_hours : "",
7768 pub_cal_free_busy ? pub_cal_free_busy : "",
7769 pub_calendar ? pub_calendar : "",
7770 pub_calendar2 ? pub_calendar2 : "",
7771 pub_oof_note ? pub_oof_note : "");
7773 send_presence_publish(sip, publications);
7774 g_free(publications);
7777 g_free(pub_cal_working_hours);
7778 g_free(pub_cal_free_busy);
7779 g_free(pub_calendar);
7780 g_free(pub_calendar2);
7781 g_free(pub_oof_note);
7783 /* repeat scheduling */
7784 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7787 static void send_presence_status(struct sipe_account_data *sip)
7789 PurpleStatus * status = purple_account_get_active_status(sip->account);
7791 if (!status) return;
7793 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7794 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7795 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7797 if (sip->ocs2007) {
7798 send_presence_category_publish(sip);
7799 } else {
7800 send_presence_soap(sip, FALSE);
7804 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7806 gboolean found = FALSE;
7807 const char *method = msg->method ? msg->method : "NOT FOUND";
7808 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,method);
7809 if (msg->response == 0) { /* request */
7810 if (sipe_strequal(method, "MESSAGE")) {
7811 process_incoming_message(sip, msg);
7812 found = TRUE;
7813 } else if (sipe_strequal(method, "NOTIFY")) {
7814 purple_debug_info("sipe","send->process_incoming_notify\n");
7815 process_incoming_notify(sip, msg, TRUE, FALSE);
7816 found = TRUE;
7817 } else if (sipe_strequal(method, "BENOTIFY")) {
7818 purple_debug_info("sipe","send->process_incoming_benotify\n");
7819 process_incoming_notify(sip, msg, TRUE, TRUE);
7820 found = TRUE;
7821 } else if (sipe_strequal(method, "INVITE")) {
7822 process_incoming_invite(sip, msg);
7823 found = TRUE;
7824 } else if (sipe_strequal(method, "REFER")) {
7825 process_incoming_refer(sip, msg);
7826 found = TRUE;
7827 } else if (sipe_strequal(method, "OPTIONS")) {
7828 process_incoming_options(sip, msg);
7829 found = TRUE;
7830 } else if (sipe_strequal(method, "INFO")) {
7831 process_incoming_info(sip, msg);
7832 found = TRUE;
7833 } else if (sipe_strequal(method, "ACK")) {
7834 // ACK's don't need any response
7835 found = TRUE;
7836 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7837 // LCS 2005 sends us these - just respond 200 OK
7838 found = TRUE;
7839 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7840 } else if (sipe_strequal(method, "BYE")) {
7841 process_incoming_bye(sip, msg);
7842 found = TRUE;
7843 } else {
7844 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7846 } else { /* response */
7847 struct transaction *trans = transactions_find(sip, msg);
7848 if (trans) {
7849 if (msg->response == 407) {
7850 gchar *resend, *auth;
7851 const gchar *ptmp;
7853 if (sip->proxy.retries > 30) return;
7854 sip->proxy.retries++;
7855 /* do proxy authentication */
7857 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7859 fill_auth(ptmp, &sip->proxy);
7860 auth = auth_header(sip, &sip->proxy, trans->msg);
7861 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7862 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7863 g_free(auth);
7864 resend = sipmsg_to_string(trans->msg);
7865 /* resend request */
7866 sendout_pkt(sip->gc, resend);
7867 g_free(resend);
7868 } else {
7869 if (msg->response < 200) {
7870 /* ignore provisional response */
7871 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
7872 } else {
7873 sip->proxy.retries = 0;
7874 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7875 if (msg->response == 401)
7877 sip->registrar.retries++;
7879 else
7881 sip->registrar.retries = 0;
7883 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
7884 } else {
7885 if (msg->response == 401) {
7886 gchar *resend, *auth, *ptmp;
7887 const char* auth_scheme;
7889 if (sip->registrar.retries > 4) return;
7890 sip->registrar.retries++;
7892 auth_scheme = sipe_get_auth_scheme_name(sip);
7893 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7895 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp ? ptmp : "");
7896 if (!ptmp) {
7897 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7898 sip->gc->wants_to_die = TRUE;
7899 purple_connection_error(sip->gc, tmp2);
7900 g_free(tmp2);
7901 return;
7904 fill_auth(ptmp, &sip->registrar);
7905 auth = auth_header(sip, &sip->registrar, trans->msg);
7906 sipmsg_remove_header_now(trans->msg, "Authorization");
7907 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7908 g_free(auth);
7909 resend = sipmsg_to_string(trans->msg);
7910 /* resend request */
7911 sendout_pkt(sip->gc, resend);
7912 g_free(resend);
7916 if (trans->callback) {
7917 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
7918 /* call the callback to process response*/
7919 (trans->callback)(sip, msg, trans);
7922 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
7923 transactions_remove(sip, trans);
7927 found = TRUE;
7928 } else {
7929 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
7932 if (!found) {
7933 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", method, msg->response);
7937 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
7939 char *cur;
7940 char *dummy;
7941 char *tmp;
7942 struct sipmsg *msg;
7943 int restlen;
7944 cur = conn->inbuf;
7946 /* according to the RFC remove CRLF at the beginning */
7947 while (*cur == '\r' || *cur == '\n') {
7948 cur++;
7950 if (cur != conn->inbuf) {
7951 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
7952 conn->inbufused = strlen(conn->inbuf);
7955 /* Received a full Header? */
7956 sip->processing_input = TRUE;
7957 while (sip->processing_input &&
7958 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
7959 time_t currtime = time(NULL);
7960 cur += 2;
7961 cur[0] = '\0';
7962 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
7963 g_free(tmp);
7964 msg = sipmsg_parse_header(conn->inbuf);
7965 cur[0] = '\r';
7966 cur += 2;
7967 restlen = conn->inbufused - (cur - conn->inbuf);
7968 if (msg && restlen >= msg->bodylen) {
7969 dummy = g_malloc(msg->bodylen + 1);
7970 memcpy(dummy, cur, msg->bodylen);
7971 dummy[msg->bodylen] = '\0';
7972 msg->body = dummy;
7973 cur += msg->bodylen;
7974 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
7975 conn->inbufused = strlen(conn->inbuf);
7976 } else {
7977 if (msg){
7978 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
7979 sipmsg_free(msg);
7981 return;
7984 /*if (msg->body) {
7985 purple_debug_info("sipe", "body:\n%s", msg->body);
7988 // Verify the signature before processing it
7989 if (sip->registrar.gssapi_context) {
7990 struct sipmsg_breakdown msgbd;
7991 gchar *signature_input_str;
7992 gchar *rspauth;
7993 msgbd.msg = msg;
7994 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
7995 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
7997 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
7999 if (rspauth != NULL) {
8000 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8001 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
8002 process_input_message(sip, msg);
8003 } else {
8004 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
8005 purple_connection_error(sip->gc, _("Invalid message signature received"));
8006 sip->gc->wants_to_die = TRUE;
8008 } else if (msg->response == 401) {
8009 purple_connection_error(sip->gc, _("Authentication failed"));
8010 sip->gc->wants_to_die = TRUE;
8012 g_free(signature_input_str);
8014 g_free(rspauth);
8015 sipmsg_breakdown_free(&msgbd);
8016 } else {
8017 process_input_message(sip, msg);
8020 sipmsg_free(msg);
8024 static void sipe_udp_process(gpointer data, gint source,
8025 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8027 PurpleConnection *gc = data;
8028 struct sipe_account_data *sip = gc->proto_data;
8029 int len;
8031 static char buffer[65536];
8032 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8033 time_t currtime = time(NULL);
8034 struct sipmsg *msg;
8035 buffer[len] = '\0';
8036 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
8037 msg = sipmsg_parse_msg(buffer);
8038 if (msg) process_input_message(sip, msg);
8042 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8044 struct sipe_account_data *sip = gc->proto_data;
8045 PurpleSslConnection *gsc = sip->gsc;
8047 purple_debug_error("sipe", "%s",debug);
8048 purple_connection_error(gc, msg);
8050 /* Invalidate this connection. Next send will open a new one */
8051 if (gsc) {
8052 connection_remove(sip, gsc->fd);
8053 purple_ssl_close(gsc);
8055 sip->gsc = NULL;
8056 sip->fd = -1;
8059 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8060 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8062 PurpleConnection *gc = data;
8063 struct sipe_account_data *sip;
8064 struct sip_connection *conn;
8065 int readlen, len;
8066 gboolean firstread = TRUE;
8068 /* NOTE: This check *IS* necessary */
8069 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8070 purple_ssl_close(gsc);
8071 return;
8074 sip = gc->proto_data;
8075 conn = connection_find(sip, gsc->fd);
8076 if (conn == NULL) {
8077 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
8078 gc->wants_to_die = TRUE;
8079 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8080 return;
8083 /* Read all available data from the SSL connection */
8084 do {
8085 /* Increase input buffer size as needed */
8086 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8087 conn->inbuflen += SIMPLE_BUF_INC;
8088 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8089 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
8092 /* Try to read as much as there is space left in the buffer */
8093 readlen = conn->inbuflen - conn->inbufused - 1;
8094 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8096 if (len < 0 && errno == EAGAIN) {
8097 /* Try again later */
8098 return;
8099 } else if (len < 0) {
8100 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8101 return;
8102 } else if (firstread && (len == 0)) {
8103 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8104 return;
8107 conn->inbufused += len;
8108 firstread = FALSE;
8110 /* Equivalence indicates that there is possibly more data to read */
8111 } while (len == readlen);
8113 conn->inbuf[conn->inbufused] = '\0';
8114 process_input(sip, conn);
8118 static void sipe_input_cb(gpointer data, gint source,
8119 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8121 PurpleConnection *gc = data;
8122 struct sipe_account_data *sip = gc->proto_data;
8123 int len;
8124 struct sip_connection *conn = connection_find(sip, source);
8125 if (!conn) {
8126 purple_debug_error("sipe", "Connection not found!\n");
8127 return;
8130 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8131 conn->inbuflen += SIMPLE_BUF_INC;
8132 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8135 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8137 if (len < 0 && errno == EAGAIN)
8138 return;
8139 else if (len <= 0) {
8140 purple_debug_info("sipe", "sipe_input_cb: read error\n");
8141 connection_remove(sip, source);
8142 if (sip->fd == source) sip->fd = -1;
8143 return;
8146 conn->inbufused += len;
8147 conn->inbuf[conn->inbufused] = '\0';
8149 process_input(sip, conn);
8152 /* Callback for new connections on incoming TCP port */
8153 static void sipe_newconn_cb(gpointer data, gint source,
8154 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8156 PurpleConnection *gc = data;
8157 struct sipe_account_data *sip = gc->proto_data;
8158 struct sip_connection *conn;
8160 int newfd = accept(source, NULL, NULL);
8162 conn = connection_create(sip, newfd);
8164 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8167 static void login_cb(gpointer data, gint source,
8168 SIPE_UNUSED_PARAMETER const gchar *error_message)
8170 PurpleConnection *gc = data;
8171 struct sipe_account_data *sip;
8172 struct sip_connection *conn;
8174 if (!PURPLE_CONNECTION_IS_VALID(gc))
8176 if (source >= 0)
8177 close(source);
8178 return;
8181 if (source < 0) {
8182 purple_connection_error(gc, _("Could not connect"));
8183 return;
8186 sip = gc->proto_data;
8187 sip->fd = source;
8188 sip->last_keepalive = time(NULL);
8190 conn = connection_create(sip, source);
8192 do_register(sip);
8194 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8197 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8198 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8200 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8201 if (sip == NULL) return;
8203 do_register(sip);
8206 static guint sipe_ht_hash_nick(const char *nick)
8208 char *lc = g_utf8_strdown(nick, -1);
8209 guint bucket = g_str_hash(lc);
8210 g_free(lc);
8212 return bucket;
8215 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8217 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
8220 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8222 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8224 sip->listen_data = NULL;
8226 if (listenfd == -1) {
8227 purple_connection_error(sip->gc, _("Could not create listen socket"));
8228 return;
8231 sip->fd = listenfd;
8233 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8234 sip->listenfd = sip->fd;
8236 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8238 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8239 do_register(sip);
8242 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8243 SIPE_UNUSED_PARAMETER const char *error_message)
8245 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8247 sip->query_data = NULL;
8249 if (!hosts || !hosts->data) {
8250 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8251 return;
8254 hosts = g_slist_remove(hosts, hosts->data);
8255 g_free(sip->serveraddr);
8256 sip->serveraddr = hosts->data;
8257 hosts = g_slist_remove(hosts, hosts->data);
8258 while (hosts) {
8259 void *tmp = hosts->data;
8260 hosts = g_slist_remove(hosts, tmp);
8261 hosts = g_slist_remove(hosts, tmp);
8262 g_free(tmp);
8265 /* create socket for incoming connections */
8266 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8267 sipe_udp_host_resolved_listen_cb, sip);
8268 if (sip->listen_data == NULL) {
8269 purple_connection_error(sip->gc, _("Could not create listen socket"));
8270 return;
8274 static const struct sipe_service_data *current_service = NULL;
8276 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8277 PurpleSslErrorType error,
8278 gpointer data)
8280 PurpleConnection *gc = data;
8281 struct sipe_account_data *sip;
8283 /* If the connection is already disconnected, we don't need to do anything else */
8284 if (!PURPLE_CONNECTION_IS_VALID(gc))
8285 return;
8287 sip = gc->proto_data;
8288 current_service = sip->service_data;
8289 if (current_service) {
8290 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8291 current_service->transport ? current_service->transport : "NULL",
8292 current_service->service ? current_service->service : "NULL");
8295 sip->fd = -1;
8296 sip->gsc = NULL;
8298 switch(error) {
8299 case PURPLE_SSL_CONNECT_FAILED:
8300 purple_connection_error(gc, _("Connection failed"));
8301 break;
8302 case PURPLE_SSL_HANDSHAKE_FAILED:
8303 purple_connection_error(gc, _("SSL handshake failed"));
8304 break;
8305 case PURPLE_SSL_CERTIFICATE_INVALID:
8306 purple_connection_error(gc, _("SSL certificate invalid"));
8307 break;
8311 static void
8312 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8314 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8315 PurpleProxyConnectData *connect_data;
8317 sip->listen_data = NULL;
8319 sip->listenfd = listenfd;
8320 if (sip->listenfd == -1) {
8321 purple_connection_error(sip->gc, _("Could not create listen socket"));
8322 return;
8325 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
8326 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8327 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8328 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8329 sipe_newconn_cb, sip->gc);
8330 purple_debug_info("sipe", "connecting to %s port %d\n",
8331 sip->realhostname, sip->realport);
8332 /* open tcp connection to the server */
8333 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8334 sip->realport, login_cb, sip->gc);
8336 if (connect_data == NULL) {
8337 purple_connection_error(sip->gc, _("Could not create socket"));
8341 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8343 PurpleAccount *account = sip->account;
8344 PurpleConnection *gc = sip->gc;
8346 if (port == 0) {
8347 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8350 sip->realhostname = hostname;
8351 sip->realport = port;
8353 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
8354 hostname, port);
8356 /* TODO: is there a good default grow size? */
8357 if (sip->transport != SIPE_TRANSPORT_UDP)
8358 sip->txbuf = purple_circ_buffer_new(0);
8360 if (sip->transport == SIPE_TRANSPORT_TLS) {
8361 /* SSL case */
8362 if (!purple_ssl_is_supported()) {
8363 gc->wants_to_die = TRUE;
8364 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8365 return;
8368 purple_debug_info("sipe", "using SSL\n");
8370 sip->gsc = purple_ssl_connect(account, hostname, port,
8371 login_cb_ssl, sipe_ssl_connect_failure, gc);
8372 if (sip->gsc == NULL) {
8373 purple_connection_error(gc, _("Could not create SSL context"));
8374 return;
8376 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8377 /* UDP case */
8378 purple_debug_info("sipe", "using UDP\n");
8380 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8381 if (sip->query_data == NULL) {
8382 purple_connection_error(gc, _("Could not resolve hostname"));
8384 } else {
8385 /* TCP case */
8386 purple_debug_info("sipe", "using TCP\n");
8387 /* create socket for incoming connections */
8388 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8389 sipe_tcp_connect_listen_cb, sip);
8390 if (sip->listen_data == NULL) {
8391 purple_connection_error(gc, _("Could not create listen socket"));
8392 return;
8397 /* Service list for autodection */
8398 static const struct sipe_service_data service_autodetect[] = {
8399 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8400 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8401 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8402 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8403 { NULL, NULL, 0 }
8406 /* Service list for SSL/TLS */
8407 static const struct sipe_service_data service_tls[] = {
8408 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8409 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8410 { NULL, NULL, 0 }
8413 /* Service list for TCP */
8414 static const struct sipe_service_data service_tcp[] = {
8415 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8416 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8417 { NULL, NULL, 0 }
8420 /* Service list for UDP */
8421 static const struct sipe_service_data service_udp[] = {
8422 { "sip", "udp", SIPE_TRANSPORT_UDP },
8423 { NULL, NULL, 0 }
8426 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8427 static void resolve_next_service(struct sipe_account_data *sip,
8428 const struct sipe_service_data *start)
8430 if (start) {
8431 sip->service_data = start;
8432 } else {
8433 sip->service_data++;
8434 if (sip->service_data->service == NULL) {
8435 gchar *hostname;
8436 /* Try connecting to the SIP hostname directly */
8437 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
8438 if (sip->auto_transport) {
8439 // If SSL is supported, default to using it; OCS servers aren't configured
8440 // by default to accept TCP
8441 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8442 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8443 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
8446 hostname = g_strdup(sip->sipdomain);
8447 create_connection(sip, hostname, 0);
8448 return;
8452 /* Try to resolve next service */
8453 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8454 sip->service_data->transport,
8455 sip->sipdomain,
8456 srvresolved, sip);
8459 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8461 struct sipe_account_data *sip = data;
8463 sip->srv_query_data = NULL;
8465 /* find the host to connect to */
8466 if (results) {
8467 gchar *hostname = g_strdup(resp->hostname);
8468 int port = resp->port;
8469 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8470 hostname, port);
8471 g_free(resp);
8473 sip->transport = sip->service_data->type;
8475 create_connection(sip, hostname, port);
8476 } else {
8477 resolve_next_service(sip, NULL);
8481 static void sipe_login(PurpleAccount *account)
8483 PurpleConnection *gc;
8484 struct sipe_account_data *sip;
8485 gchar **signinname_login, **userserver;
8486 const char *transport;
8487 const char *email;
8489 const char *username = purple_account_get_username(account);
8490 gc = purple_account_get_connection(account);
8492 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
8494 if (strpbrk(username, "\t\v\r\n") != NULL) {
8495 gc->wants_to_die = TRUE;
8496 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8497 return;
8500 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8501 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8502 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8503 sip->gc = gc;
8504 sip->account = account;
8505 sip->reregister_set = FALSE;
8506 sip->reauthenticate_set = FALSE;
8507 sip->subscribed = FALSE;
8508 sip->subscribed_buddies = FALSE;
8509 sip->initial_state_published = FALSE;
8511 /* username format: <username>,[<optional login>] */
8512 signinname_login = g_strsplit(username, ",", 2);
8513 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
8515 /* ensure that username format is name@domain */
8516 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8517 g_strfreev(signinname_login);
8518 gc->wants_to_die = TRUE;
8519 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8520 return;
8522 sip->username = g_strdup(signinname_login[0]);
8524 /* ensure that email format is name@domain if provided */
8525 email = purple_account_get_string(sip->account, "email", NULL);
8526 if (!is_empty(email) &&
8527 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8529 gc->wants_to_die = TRUE;
8530 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8531 return;
8533 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8535 /* login name specified? */
8536 if (signinname_login[1] && strlen(signinname_login[1])) {
8537 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8538 gboolean has_domain = domain_user[1] != NULL;
8539 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
8540 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8541 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8542 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8543 sip->authdomain ? sip->authdomain : "", sip->authuser);
8544 g_strfreev(domain_user);
8547 userserver = g_strsplit(signinname_login[0], "@", 2);
8548 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
8549 purple_connection_set_display_name(gc, userserver[0]);
8550 sip->sipdomain = g_strdup(userserver[1]);
8551 g_strfreev(userserver);
8552 g_strfreev(signinname_login);
8554 if (strchr(sip->username, ' ') != NULL) {
8555 gc->wants_to_die = TRUE;
8556 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8557 return;
8560 sip->password = g_strdup(purple_connection_get_password(gc));
8562 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8563 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8564 g_free, (GDestroyNotify)g_hash_table_destroy);
8565 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8566 g_free, (GDestroyNotify)sipe_subscription_free);
8568 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8570 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8572 g_free(sip->status);
8573 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8575 sip->auto_transport = FALSE;
8576 transport = purple_account_get_string(account, "transport", "auto");
8577 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
8578 if (userserver[0]) {
8579 /* Use user specified server[:port] */
8580 int port = 0;
8582 if (userserver[1])
8583 port = atoi(userserver[1]);
8585 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8586 userserver[0], port);
8588 if (sipe_strequal(transport, "auto")) {
8589 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8590 } else if (sipe_strequal(transport, "tls")) {
8591 sip->transport = SIPE_TRANSPORT_TLS;
8592 } else if (sipe_strequal(transport, "tcp")) {
8593 sip->transport = SIPE_TRANSPORT_TCP;
8594 } else {
8595 sip->transport = SIPE_TRANSPORT_UDP;
8598 create_connection(sip, g_strdup(userserver[0]), port);
8599 } else {
8600 /* Server auto-discovery */
8601 if (sipe_strequal(transport, "auto")) {
8602 sip->auto_transport = TRUE;
8603 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
8604 current_service++;
8605 resolve_next_service(sip, current_service);
8606 } else {
8607 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
8609 } else if (sipe_strequal(transport, "tls")) {
8610 resolve_next_service(sip, service_tls);
8611 } else if (sipe_strequal(transport, "tcp")) {
8612 resolve_next_service(sip, service_tcp);
8613 } else {
8614 resolve_next_service(sip, service_udp);
8617 g_strfreev(userserver);
8620 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8622 connection_free_all(sip);
8624 g_free(sip->epid);
8625 sip->epid = NULL;
8627 if (sip->query_data != NULL)
8628 purple_dnsquery_destroy(sip->query_data);
8629 sip->query_data = NULL;
8631 if (sip->srv_query_data != NULL)
8632 purple_srv_cancel(sip->srv_query_data);
8633 sip->srv_query_data = NULL;
8635 if (sip->listen_data != NULL)
8636 purple_network_listen_cancel(sip->listen_data);
8637 sip->listen_data = NULL;
8639 if (sip->gsc != NULL)
8640 purple_ssl_close(sip->gsc);
8641 sip->gsc = NULL;
8643 sipe_auth_free(&sip->registrar);
8644 sipe_auth_free(&sip->proxy);
8646 if (sip->txbuf)
8647 purple_circ_buffer_destroy(sip->txbuf);
8648 sip->txbuf = NULL;
8650 g_free(sip->realhostname);
8651 sip->realhostname = NULL;
8653 g_free(sip->server_version);
8654 sip->server_version = NULL;
8656 if (sip->listenpa)
8657 purple_input_remove(sip->listenpa);
8658 sip->listenpa = 0;
8659 if (sip->tx_handler)
8660 purple_input_remove(sip->tx_handler);
8661 sip->tx_handler = 0;
8662 if (sip->resendtimeout)
8663 purple_timeout_remove(sip->resendtimeout);
8664 sip->resendtimeout = 0;
8665 if (sip->timeouts) {
8666 GSList *entry = sip->timeouts;
8667 while (entry) {
8668 struct scheduled_action *sched_action = entry->data;
8669 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
8670 purple_timeout_remove(sched_action->timeout_handler);
8671 if (sched_action->destroy) {
8672 (*sched_action->destroy)(sched_action->payload);
8674 g_free(sched_action->name);
8675 g_free(sched_action);
8676 entry = entry->next;
8679 g_slist_free(sip->timeouts);
8681 if (sip->allow_events) {
8682 GSList *entry = sip->allow_events;
8683 while (entry) {
8684 g_free(entry->data);
8685 entry = entry->next;
8688 g_slist_free(sip->allow_events);
8690 if (sip->containers) {
8691 GSList *entry = sip->containers;
8692 while (entry) {
8693 free_container((struct sipe_container *)entry->data);
8694 entry = entry->next;
8697 g_slist_free(sip->containers);
8699 if (sip->contact)
8700 g_free(sip->contact);
8701 sip->contact = NULL;
8702 if (sip->regcallid)
8703 g_free(sip->regcallid);
8704 sip->regcallid = NULL;
8706 if (sip->serveraddr)
8707 g_free(sip->serveraddr);
8708 sip->serveraddr = NULL;
8710 if (sip->focus_factory_uri)
8711 g_free(sip->focus_factory_uri);
8712 sip->focus_factory_uri = NULL;
8714 sip->fd = -1;
8715 sip->processing_input = FALSE;
8717 if (sip->ews) {
8718 sipe_ews_free(sip->ews);
8720 sip->ews = NULL;
8724 * A callback for g_hash_table_foreach_remove
8726 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8727 SIPE_UNUSED_PARAMETER gpointer user_data)
8729 sipe_free_buddy((struct sipe_buddy *) buddy);
8731 /* We must return TRUE as the key/value have already been deleted */
8732 return(TRUE);
8735 static void sipe_close(PurpleConnection *gc)
8737 struct sipe_account_data *sip = gc->proto_data;
8739 if (sip) {
8740 /* leave all conversations */
8741 sipe_session_close_all(sip);
8742 sipe_session_remove_all(sip);
8744 if (sip->csta) {
8745 sip_csta_close(sip);
8748 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8749 /* unsubscribe all */
8750 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8752 /* unregister */
8753 do_register_exp(sip, 0);
8756 sipe_connection_cleanup(sip);
8757 g_free(sip->sipdomain);
8758 g_free(sip->username);
8759 g_free(sip->email);
8760 g_free(sip->password);
8761 g_free(sip->authdomain);
8762 g_free(sip->authuser);
8763 g_free(sip->status);
8764 g_free(sip->note);
8765 g_free(sip->user_states);
8767 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
8768 g_hash_table_destroy(sip->buddies);
8769 g_hash_table_destroy(sip->our_publications);
8770 g_hash_table_destroy(sip->user_state_publications);
8771 g_hash_table_destroy(sip->subscriptions);
8772 g_hash_table_destroy(sip->filetransfers);
8774 if (sip->groups) {
8775 GSList *entry = sip->groups;
8776 while (entry) {
8777 struct sipe_group *group = entry->data;
8778 g_free(group->name);
8779 g_free(group);
8780 entry = entry->next;
8783 g_slist_free(sip->groups);
8785 if (sip->our_publication_keys) {
8786 GSList *entry = sip->our_publication_keys;
8787 while (entry) {
8788 g_free(entry->data);
8789 entry = entry->next;
8792 g_slist_free(sip->our_publication_keys);
8794 while (sip->transactions)
8795 transactions_remove(sip, sip->transactions->data);
8797 g_free(gc->proto_data);
8798 gc->proto_data = NULL;
8801 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8802 SIPE_UNUSED_PARAMETER void *user_data)
8804 PurpleAccount *acct = purple_connection_get_account(gc);
8805 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8806 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8807 if (conv == NULL)
8808 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8809 purple_conversation_present(conv);
8810 g_free(id);
8813 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8814 SIPE_UNUSED_PARAMETER void *user_data)
8817 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8818 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8821 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8822 SIPE_UNUSED_PARAMETER struct transaction *trans)
8824 PurpleNotifySearchResults *results;
8825 PurpleNotifySearchColumn *column;
8826 xmlnode *searchResults;
8827 xmlnode *mrow;
8828 int match_count = 0;
8829 gboolean more = FALSE;
8830 gchar *secondary;
8832 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
8834 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
8835 if (!searchResults) {
8836 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8837 return FALSE;
8840 results = purple_notify_searchresults_new();
8842 if (results == NULL) {
8843 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8844 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8846 xmlnode_free(searchResults);
8847 return FALSE;
8850 column = purple_notify_searchresults_column_new(_("User name"));
8851 purple_notify_searchresults_column_add(results, column);
8853 column = purple_notify_searchresults_column_new(_("Name"));
8854 purple_notify_searchresults_column_add(results, column);
8856 column = purple_notify_searchresults_column_new(_("Company"));
8857 purple_notify_searchresults_column_add(results, column);
8859 column = purple_notify_searchresults_column_new(_("Country"));
8860 purple_notify_searchresults_column_add(results, column);
8862 column = purple_notify_searchresults_column_new(_("Email"));
8863 purple_notify_searchresults_column_add(results, column);
8865 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
8866 GList *row = NULL;
8868 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
8869 row = g_list_append(row, g_strdup(uri_parts[1]));
8870 g_strfreev(uri_parts);
8872 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
8873 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
8874 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
8875 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
8877 purple_notify_searchresults_row_add(results, row);
8878 match_count++;
8881 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
8882 char *data = xmlnode_get_data(mrow);
8883 more = (g_strcasecmp(data, "true") == 0);
8884 g_free(data);
8887 secondary = g_strdup_printf(
8888 dngettext(PACKAGE_NAME,
8889 "Found %d contact%s:",
8890 "Found %d contacts%s:", match_count),
8891 match_count, more ? _(" (more matched your query)") : "");
8893 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8894 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8895 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8897 g_free(secondary);
8898 xmlnode_free(searchResults);
8899 return TRUE;
8902 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8904 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8905 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8906 unsigned i = 0;
8908 if (!attrs) return;
8910 do {
8911 PurpleRequestField *field = entries->data;
8912 const char *id = purple_request_field_get_id(field);
8913 const char *value = purple_request_field_string_get_value(field);
8915 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
8917 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8918 } while ((entries = g_list_next(entries)) != NULL);
8919 attrs[i] = NULL;
8921 if (i > 0) {
8922 struct sipe_account_data *sip = gc->proto_data;
8923 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
8924 gchar *query = g_strjoinv(NULL, attrs);
8925 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8926 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
8927 send_soap_request_with_cb(sip, domain_uri, body,
8928 (TransCallback) process_search_contact_response, NULL);
8929 g_free(domain_uri);
8930 g_free(body);
8931 g_free(query);
8934 g_strfreev(attrs);
8937 static void sipe_show_find_contact(PurplePluginAction *action)
8939 PurpleConnection *gc = (PurpleConnection *) action->context;
8940 PurpleRequestFields *fields;
8941 PurpleRequestFieldGroup *group;
8942 PurpleRequestField *field;
8944 fields = purple_request_fields_new();
8945 group = purple_request_field_group_new(NULL);
8946 purple_request_fields_add_group(fields, group);
8948 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
8949 purple_request_field_group_add_field(group, field);
8950 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
8951 purple_request_field_group_add_field(group, field);
8952 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
8953 purple_request_field_group_add_field(group, field);
8954 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
8955 purple_request_field_group_add_field(group, field);
8957 purple_request_fields(gc,
8958 _("Search"),
8959 _("Search for a contact"),
8960 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8961 fields,
8962 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
8963 _("_Cancel"), NULL,
8964 purple_connection_get_account(gc), NULL, NULL, gc);
8967 static void sipe_show_about_plugin(PurplePluginAction *action)
8969 PurpleConnection *gc = (PurpleConnection *) action->context;
8970 char *tmp = g_strdup_printf(
8972 * Non-translatable parts, like markup, are hard-coded
8973 * into the format string. This requires more translatable
8974 * texts but it makes the translations less error prone.
8976 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
8977 "<br/>"
8978 /* 1 */ "%s:<br/>"
8979 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8980 "<li> - MS Office Communications Server 2007</li><br/>"
8981 "<li> - MS Live Communications Server 2005</li><br/>"
8982 "<li> - MS Live Communications Server 2003</li><br/>"
8983 "<li> - Reuters Messaging</li><br/>"
8984 "<br/>"
8985 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
8986 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8987 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
8988 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8989 /* 8 */ "%s: GPLv2+<br/>"
8990 "<br/>"
8991 /* 9 */ "%s:<br/>"
8992 " - CERN<br/>"
8993 " - Reuters Messaging network<br/>"
8994 " - Deutsche Bank<br/>"
8995 " - Merrill Lynch<br/>"
8996 " - Wachovia<br/>"
8997 " - Intel<br/>"
8998 " - Nokia<br/>"
8999 " - HP<br/>"
9000 " - Symantec<br/>"
9001 " - Accenture<br/>"
9002 " - Capgemini<br/>"
9003 " - Siemens<br/>"
9004 " - Alcatel-Lucent<br/>"
9005 " - BT<br/>"
9006 "<br/>"
9007 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9008 "<br/>"
9009 /* 12 */ "<b>%s:</b><br/>"
9010 " - Anibal Avelar<br/>"
9011 " - Gabriel Burt<br/>"
9012 " - Stefan Becker<br/>"
9013 " - pier11<br/>"
9014 " - Jakub Adam<br/>"
9015 " - Tomáš Hrabčík<br/>"
9016 "<br/>"
9017 /* 13 */ "%s<br/>"
9019 /* The next 13 texts make up the SIPE about note text */
9020 /* About note, part 1/13: introduction */
9021 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9022 /* About note, part 2/13: home page URL (label) */
9023 _("Home"),
9024 /* About note, part 3/13: support forum URL (label) */
9025 _("Support"),
9026 /* About note, part 4/13: support forum name (hyperlink text) */
9027 _("Help Forum"),
9028 /* About note, part 5/13: bug tracker URL (label) */
9029 _("Report Problems"),
9030 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9031 _("Bug Tracker"),
9032 /* About note, part 7/13: translation service URL (label) */
9033 _("Translations"),
9034 /* About note, part 8/13: license type (label) */
9035 _("License"),
9036 /* About note, part 9/13: known users */
9037 _("We support users in such organizations as"),
9038 /* About note, part 10/13: translation request, text before Transifex.net URL */
9039 /* append a space if text is not empty */
9040 _("Please help us to translate SIPE to your native language here at "),
9041 /* About note, part 11/13: translation request, text after Transifex.net URL */
9042 /* start with a space if text is not empty */
9043 _(" using convenient web interface"),
9044 /* About note, part 12/13: author list (header) */
9045 _("Authors"),
9046 /* About note, part 13/13: Localization credit */
9047 /* PLEASE NOTE: do *NOT* simply translate the english original */
9048 /* but write something similar to the following sentence: */
9049 /* "Localization for <language name> (<language code>): <name>" */
9050 _("Original texts in English (en): SIPE developers")
9052 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9053 g_free(tmp);
9056 static void sipe_republish_calendar(PurplePluginAction *action)
9058 PurpleConnection *gc = (PurpleConnection *) action->context;
9059 struct sipe_account_data *sip = gc->proto_data;
9061 sipe_update_calendar(sip);
9064 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9065 gpointer value,
9066 GString* str)
9068 struct sipe_publication *publication = value;
9070 g_string_append_printf( str,
9071 SIPE_PUB_XML_PUBLICATION_CLEAR,
9072 publication->category,
9073 publication->instance,
9074 publication->container,
9075 publication->version,
9076 "static");
9079 static void sipe_reset_status(PurplePluginAction *action)
9081 PurpleConnection *gc = (PurpleConnection *) action->context;
9082 struct sipe_account_data *sip = gc->proto_data;
9084 if (sip->ocs2007) /* 2007+ */
9086 GString* str = g_string_new(NULL);
9087 gchar *publications;
9089 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9090 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
9091 return;
9094 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9095 publications = g_string_free(str, FALSE);
9097 send_presence_publish(sip, publications);
9098 g_free(publications);
9100 else /* 2005 */
9102 send_presence_soap0(sip, FALSE, TRUE);
9106 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9107 gpointer context)
9109 PurpleConnection *gc = (PurpleConnection *)context;
9110 struct sipe_account_data *sip = gc->proto_data;
9111 GList *menu = NULL;
9112 PurplePluginAction *act;
9113 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9115 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9116 menu = g_list_prepend(menu, act);
9118 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9119 menu = g_list_prepend(menu, act);
9121 if (sipe_strequal(calendar, "EXCH")) {
9122 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9123 menu = g_list_prepend(menu, act);
9126 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9127 menu = g_list_prepend(menu, act);
9129 menu = g_list_reverse(menu);
9131 return menu;
9134 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9138 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9140 return TRUE;
9144 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9146 return TRUE;
9150 static char *sipe_status_text(PurpleBuddy *buddy)
9152 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9153 const PurpleStatus *status = purple_presence_get_active_status(presence);
9154 const char *status_id = purple_status_get_id(status);
9155 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9156 struct sipe_buddy *sbuddy;
9157 char *text = NULL;
9159 if (!sip) return NULL; /* happens on pidgin exit */
9161 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9162 if (sbuddy) {
9163 const char *activity_str = sbuddy->activity ?
9164 sbuddy->activity :
9165 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9166 purple_status_get_name(status) : NULL;
9168 if (activity_str && sbuddy->note)
9170 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9172 else if (activity_str)
9174 text = g_strdup(activity_str);
9176 else if (sbuddy->note)
9178 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9182 return text;
9185 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9187 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9188 const PurpleStatus *status = purple_presence_get_active_status(presence);
9189 struct sipe_account_data *sip;
9190 struct sipe_buddy *sbuddy;
9191 char *note = NULL;
9192 gboolean is_oof_note = FALSE;
9193 char *activity = NULL;
9194 char *calendar = NULL;
9195 char *meeting_subject = NULL;
9196 char *meeting_location = NULL;
9198 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9199 if (sip) //happens on pidgin exit
9201 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9202 if (sbuddy)
9204 note = sbuddy->note;
9205 is_oof_note = sbuddy->is_oof_note;
9206 activity = sbuddy->activity;
9207 calendar = sipe_cal_get_description(sbuddy);
9208 meeting_subject = sbuddy->meeting_subject;
9209 meeting_location = sbuddy->meeting_location;
9213 //Layout
9214 if (purple_presence_is_online(presence))
9216 const char *status_str = activity ? activity : purple_status_get_name(status);
9218 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9220 if (purple_presence_is_online(presence) &&
9221 !is_empty(calendar))
9223 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9225 g_free(calendar);
9226 if (!is_empty(meeting_location))
9228 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9230 if (!is_empty(meeting_subject))
9232 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9235 if (note)
9237 char *tmp = g_strdup_printf("<i>%s</i>", note);
9238 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, note);
9240 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9241 g_free(tmp);
9246 #if PURPLE_VERSION_CHECK(2,5,0)
9247 static GHashTable *
9248 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9250 GHashTable *table;
9251 table = g_hash_table_new(g_str_hash, g_str_equal);
9252 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9253 return table;
9255 #endif
9257 static PurpleBuddy *
9258 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9260 PurpleBuddy *clone;
9261 const gchar *server_alias, *email;
9262 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9264 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9266 purple_blist_add_buddy(clone, NULL, group, NULL);
9268 server_alias = purple_buddy_get_server_alias(buddy);
9269 if (server_alias) {
9270 purple_blist_server_alias_buddy(clone, server_alias);
9273 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9274 if (email) {
9275 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9278 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9279 //for UI to update;
9280 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9281 return clone;
9284 static void
9285 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9287 PurpleBuddy *buddy, *b;
9288 PurpleConnection *gc;
9289 PurpleGroup * group = purple_find_group(group_name);
9291 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9293 buddy = (PurpleBuddy *)node;
9295 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
9296 gc = purple_account_get_connection(buddy->account);
9298 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9299 if (!b){
9300 purple_blist_add_buddy_clone(group, buddy);
9303 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9306 static void
9307 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9309 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9311 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
9313 /* 2007+ conference */
9314 if (sip->ocs2007)
9316 sipe_conf_add(sip, buddy->name);
9318 else /* 2005- multiparty chat */
9320 gchar *self = sip_uri_self(sip);
9321 struct sip_session *session;
9323 session = sipe_session_add_chat(sip);
9324 session->chat_title = sipe_chat_get_name(session->callid);
9325 session->roster_manager = g_strdup(self);
9327 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9328 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9329 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9330 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9332 g_free(self);
9336 static gboolean
9337 sipe_is_election_finished(struct sip_session *session)
9339 gboolean res = TRUE;
9341 SIPE_DIALOG_FOREACH {
9342 if (dialog->election_vote == 0) {
9343 res = FALSE;
9344 break;
9346 } SIPE_DIALOG_FOREACH_END;
9348 if (res) {
9349 session->is_voting_in_progress = FALSE;
9351 return res;
9354 static void
9355 sipe_election_start(struct sipe_account_data *sip,
9356 struct sip_session *session)
9358 int election_timeout;
9360 if (session->is_voting_in_progress) {
9361 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9362 return;
9363 } else {
9364 session->is_voting_in_progress = TRUE;
9366 session->bid = rand();
9368 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
9370 SIPE_DIALOG_FOREACH {
9371 /* reset election_vote for each chat participant */
9372 dialog->election_vote = 0;
9374 /* send RequestRM to each chat participant*/
9375 sipe_send_election_request_rm(sip, dialog, session->bid);
9376 } SIPE_DIALOG_FOREACH_END;
9378 election_timeout = 15; /* sec */
9379 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9383 * @param who a URI to whom to invite to chat
9385 void
9386 sipe_invite_to_chat(struct sipe_account_data *sip,
9387 struct sip_session *session,
9388 const gchar *who)
9390 /* a conference */
9391 if (session->focus_uri)
9393 sipe_invite_conf(sip, session, who);
9395 else /* a multi-party chat */
9397 gchar *self = sip_uri_self(sip);
9398 if (session->roster_manager) {
9399 if (sipe_strcase_equal(session->roster_manager, self)) {
9400 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9401 } else {
9402 sipe_refer(sip, session, who);
9404 } else {
9405 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9407 session->pending_invite_queue = slist_insert_unique_sorted(
9408 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9410 sipe_election_start(sip, session);
9412 g_free(self);
9416 void
9417 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9418 struct sip_session *session)
9420 gchar *invitee;
9421 GSList *entry = session->pending_invite_queue;
9423 while (entry) {
9424 invitee = entry->data;
9425 sipe_invite_to_chat(sip, session, invitee);
9426 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9427 g_free(invitee);
9431 static void
9432 sipe_election_result(struct sipe_account_data *sip,
9433 void *sess)
9435 struct sip_session *session = (struct sip_session *)sess;
9436 gchar *rival;
9437 gboolean has_won = TRUE;
9439 if (session->roster_manager) {
9440 purple_debug_info("sipe",
9441 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
9442 return;
9445 session->is_voting_in_progress = FALSE;
9447 SIPE_DIALOG_FOREACH {
9448 if (dialog->election_vote < 0) {
9449 has_won = FALSE;
9450 rival = dialog->with;
9451 break;
9453 } SIPE_DIALOG_FOREACH_END;
9455 if (has_won) {
9456 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9458 session->roster_manager = sip_uri_self(sip);
9460 SIPE_DIALOG_FOREACH {
9461 /* send SetRM to each chat participant*/
9462 sipe_send_election_set_rm(sip, dialog);
9463 } SIPE_DIALOG_FOREACH_END;
9464 } else {
9465 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
9467 session->bid = 0;
9469 sipe_process_pending_invite_queue(sip, session);
9473 * For 2007+ conference only.
9475 static void
9476 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9478 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9479 struct sip_session *session;
9481 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
9482 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
9484 session = sipe_session_find_chat_by_title(sip, chat_title);
9486 sipe_conf_modify_user_role(sip, session, buddy->name);
9490 * For 2007+ conference only.
9492 static void
9493 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9495 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9496 struct sip_session *session;
9498 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
9499 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
9501 session = sipe_session_find_chat_by_title(sip, chat_title);
9503 sipe_conf_delete_user(sip, session, buddy->name);
9506 static void
9507 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9509 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9510 struct sip_session *session;
9512 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
9513 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
9515 session = sipe_session_find_chat_by_title(sip, chat_title);
9517 sipe_invite_to_chat(sip, session, buddy->name);
9520 static void
9521 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9523 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9525 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
9526 if (phone) {
9527 char *tel_uri = sip_to_tel_uri(phone);
9529 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
9530 sip_csta_make_call(sip, tel_uri);
9532 g_free(tel_uri);
9536 static void
9537 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9539 const gchar *email;
9540 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
9542 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9543 if (email)
9545 char *mailto = g_strdup_printf("mailto:%s", email);
9546 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
9547 #ifndef _WIN32
9549 pid_t pid;
9550 char *const parmList[] = {"xdg-email", mailto, NULL};
9551 if ((pid = fork()) == -1)
9553 purple_debug_info("sipe", "fork() error\n");
9555 else if (pid == 0)
9557 execvp(parmList[0], parmList);
9558 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9561 #else
9563 BOOL ret;
9564 _flushall();
9565 errno = 0;
9566 //@TODO resolve env variable %WINDIR% first
9567 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
9568 if (errno)
9570 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
9573 #endif
9575 g_free(mailto);
9577 else
9579 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
9584 * A menu which appear when right-clicking on buddy in contact list.
9586 static GList *
9587 sipe_buddy_menu(PurpleBuddy *buddy)
9589 PurpleBlistNode *g_node;
9590 PurpleGroup *group, *gr_parent;
9591 PurpleMenuAction *act;
9592 GList *menu = NULL;
9593 GList *menu_groups = NULL;
9594 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9595 const char *email;
9596 const char *phone;
9597 const char *phone_disp_str;
9598 gchar *self = sip_uri_self(sip);
9600 SIPE_SESSION_FOREACH {
9601 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9603 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9605 PurpleConvChatBuddyFlags flags;
9606 PurpleConvChatBuddyFlags flags_us;
9608 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9609 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9610 if (session->focus_uri
9611 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9612 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9614 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9615 act = purple_menu_action_new(label,
9616 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9617 session->chat_title, NULL);
9618 g_free(label);
9619 menu = g_list_prepend(menu, act);
9622 if (session->focus_uri
9623 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9625 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9626 act = purple_menu_action_new(label,
9627 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9628 session->chat_title, NULL);
9629 g_free(label);
9630 menu = g_list_prepend(menu, act);
9633 else
9635 if (!session->focus_uri
9636 || (session->focus_uri && !session->locked))
9638 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9639 act = purple_menu_action_new(label,
9640 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9641 session->chat_title, NULL);
9642 g_free(label);
9643 menu = g_list_prepend(menu, act);
9647 } SIPE_SESSION_FOREACH_END;
9649 act = purple_menu_action_new(_("New chat"),
9650 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9651 NULL, NULL);
9652 menu = g_list_prepend(menu, act);
9654 if (sip->csta && !sip->csta->line_status) {
9655 gchar *tmp = NULL;
9656 /* work phone */
9657 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9658 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9659 if (phone) {
9660 gchar *label = g_strdup_printf(_("Work %s"),
9661 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9662 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9663 g_free(tmp);
9664 tmp = NULL;
9665 g_free(label);
9666 menu = g_list_prepend(menu, act);
9669 /* mobile phone */
9670 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9671 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9672 if (phone) {
9673 gchar *label = g_strdup_printf(_("Mobile %s"),
9674 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9675 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9676 g_free(tmp);
9677 tmp = NULL;
9678 g_free(label);
9679 menu = g_list_prepend(menu, act);
9682 /* home phone */
9683 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9684 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9685 if (phone) {
9686 gchar *label = g_strdup_printf(_("Home %s"),
9687 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9688 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9689 g_free(tmp);
9690 tmp = NULL;
9691 g_free(label);
9692 menu = g_list_prepend(menu, act);
9695 /* other phone */
9696 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9697 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9698 if (phone) {
9699 gchar *label = g_strdup_printf(_("Other %s"),
9700 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9701 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9702 g_free(tmp);
9703 tmp = NULL;
9704 g_free(label);
9705 menu = g_list_prepend(menu, act);
9708 /* custom1 phone */
9709 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9710 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9711 if (phone) {
9712 gchar *label = g_strdup_printf(_("Custom1 %s"),
9713 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9714 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9715 g_free(tmp);
9716 tmp = NULL;
9717 g_free(label);
9718 menu = g_list_prepend(menu, act);
9722 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9723 if (email) {
9724 act = purple_menu_action_new(_("Send email..."),
9725 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9726 NULL, NULL);
9727 menu = g_list_prepend(menu, act);
9730 gr_parent = purple_buddy_get_group(buddy);
9731 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9732 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9733 continue;
9735 group = (PurpleGroup *)g_node;
9736 if (group == gr_parent)
9737 continue;
9739 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9740 continue;
9742 act = purple_menu_action_new(purple_group_get_name(group),
9743 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9744 group->name, NULL);
9745 menu_groups = g_list_prepend(menu_groups, act);
9747 menu_groups = g_list_reverse(menu_groups);
9749 act = purple_menu_action_new(_("Copy to"),
9750 NULL,
9751 NULL, menu_groups);
9752 menu = g_list_prepend(menu, act);
9753 menu = g_list_reverse(menu);
9755 g_free(self);
9756 return menu;
9759 static void
9760 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9762 struct sipe_account_data *sip = chat->account->gc->proto_data;
9763 struct sip_session *session;
9765 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9766 sipe_conf_modify_conference_lock(sip, session, locked);
9769 static void
9770 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9772 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9773 sipe_conf_modify_lock(chat, FALSE);
9776 static void
9777 sipe_chat_menu_lock_cb(PurpleChat *chat)
9779 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9780 sipe_conf_modify_lock(chat, TRUE);
9783 static GList *
9784 sipe_chat_menu(PurpleChat *chat)
9786 PurpleMenuAction *act;
9787 PurpleConvChatBuddyFlags flags_us;
9788 GList *menu = NULL;
9789 struct sipe_account_data *sip = chat->account->gc->proto_data;
9790 struct sip_session *session;
9791 gchar *self;
9793 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9794 if (!session) return NULL;
9796 self = sip_uri_self(sip);
9797 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9799 if (session->focus_uri
9800 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9802 if (session->locked) {
9803 act = purple_menu_action_new(_("Unlock"),
9804 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9805 NULL, NULL);
9806 menu = g_list_prepend(menu, act);
9807 } else {
9808 act = purple_menu_action_new(_("Lock"),
9809 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9810 NULL, NULL);
9811 menu = g_list_prepend(menu, act);
9815 menu = g_list_reverse(menu);
9817 g_free(self);
9818 return menu;
9821 static GList *
9822 sipe_blist_node_menu(PurpleBlistNode *node)
9824 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
9825 return sipe_buddy_menu((PurpleBuddy *) node);
9826 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
9827 return sipe_chat_menu((PurpleChat *)node);
9828 } else {
9829 return NULL;
9833 static gboolean
9834 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9836 char *uri = trans->payload->data;
9838 PurpleNotifyUserInfo *info;
9839 PurpleBuddy *pbuddy = NULL;
9840 struct sipe_buddy *sbuddy;
9841 const char *alias = NULL;
9842 char *device_name = NULL;
9843 char *server_alias = NULL;
9844 char *phone_number = NULL;
9845 char *email = NULL;
9846 const char *site;
9847 char *first_name = NULL;
9848 char *last_name = NULL;
9850 if (!sip) return FALSE;
9852 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
9854 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9855 alias = purple_buddy_get_local_alias(pbuddy);
9857 //will query buddy UA's capabilities and send answer to log
9858 sipe_options_request(sip, uri);
9860 sbuddy = g_hash_table_lookup(sip->buddies, uri);
9861 if (sbuddy) {
9862 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9865 info = purple_notify_user_info_new();
9867 if (msg->response != 200) {
9868 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
9869 } else {
9870 xmlnode *searchResults;
9871 xmlnode *mrow;
9873 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
9874 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
9875 if (!searchResults) {
9876 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9877 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
9878 const char *value;
9879 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
9880 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
9881 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
9883 /* For 2007 system we will take this from ContactCard -
9884 * it has cleaner tel: URIs at least
9886 if (!sip->ocs2007) {
9887 char *tel_uri = sip_to_tel_uri(phone_number);
9888 /* trims its parameters, so call first */
9889 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9890 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9891 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9892 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9893 g_free(tel_uri);
9896 if (server_alias && strlen(server_alias) > 0) {
9897 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9899 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
9900 purple_notify_user_info_add_pair(info, _("Job title"), value);
9902 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
9903 purple_notify_user_info_add_pair(info, _("Office"), value);
9905 if (phone_number && strlen(phone_number) > 0) {
9906 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9908 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
9909 purple_notify_user_info_add_pair(info, _("Company"), value);
9911 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
9912 purple_notify_user_info_add_pair(info, _("City"), value);
9914 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
9915 purple_notify_user_info_add_pair(info, _("State"), value);
9917 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
9918 purple_notify_user_info_add_pair(info, _("Country"), value);
9920 if (email && strlen(email) > 0) {
9921 purple_notify_user_info_add_pair(info, _("Email address"), email);
9925 xmlnode_free(searchResults);
9928 purple_notify_user_info_add_section_break(info);
9930 if (is_empty(server_alias)) {
9931 g_free(server_alias);
9932 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9933 if (server_alias) {
9934 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9938 /* present alias if it differs from server alias */
9939 if (alias && !sipe_strequal(alias, server_alias))
9941 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9944 if (is_empty(email)) {
9945 g_free(email);
9946 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9947 if (email) {
9948 purple_notify_user_info_add_pair(info, _("Email address"), email);
9952 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9953 if (site) {
9954 purple_notify_user_info_add_pair(info, _("Site"), site);
9957 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9958 if (first_name && last_name) {
9959 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9961 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9962 g_free(link);
9964 g_free(first_name);
9965 g_free(last_name);
9967 if (device_name) {
9968 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9971 /* show a buddy's user info in a nice dialog box */
9972 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9973 uri, /* buddy's URI */
9974 info, /* body */
9975 NULL, /* callback called when dialog closed */
9976 NULL); /* userdata for callback */
9978 g_free(phone_number);
9979 g_free(server_alias);
9980 g_free(email);
9981 g_free(device_name);
9983 return TRUE;
9987 * AD search first, LDAP based
9989 static void sipe_get_info(PurpleConnection *gc, const char *username)
9991 struct sipe_account_data *sip = gc->proto_data;
9992 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9993 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9994 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9995 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9997 payload->destroy = g_free;
9998 payload->data = g_strdup(username);
10000 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
10001 send_soap_request_with_cb(sip, domain_uri, body,
10002 (TransCallback) process_get_info_response, payload);
10003 g_free(domain_uri);
10004 g_free(body);
10005 g_free(row);
10008 PurplePluginProtocolInfo prpl_info =
10010 OPT_PROTO_CHAT_TOPIC,
10011 NULL, /* user_splits */
10012 NULL, /* protocol_options */
10013 NO_BUDDY_ICONS, /* icon_spec */
10014 sipe_list_icon, /* list_icon */
10015 NULL, /* list_emblems */
10016 sipe_status_text, /* status_text */
10017 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10018 sipe_status_types, /* away_states */
10019 sipe_blist_node_menu, /* blist_node_menu */
10020 NULL, /* chat_info */
10021 NULL, /* chat_info_defaults */
10022 sipe_login, /* login */
10023 sipe_close, /* close */
10024 sipe_im_send, /* send_im */
10025 NULL, /* set_info */ // TODO maybe
10026 sipe_send_typing, /* send_typing */
10027 sipe_get_info, /* get_info */
10028 sipe_set_status, /* set_status */
10029 sipe_set_idle, /* set_idle */
10030 NULL, /* change_passwd */
10031 sipe_add_buddy, /* add_buddy */
10032 NULL, /* add_buddies */
10033 sipe_remove_buddy, /* remove_buddy */
10034 NULL, /* remove_buddies */
10035 sipe_add_permit, /* add_permit */
10036 sipe_add_deny, /* add_deny */
10037 sipe_add_deny, /* rem_permit */
10038 sipe_add_permit, /* rem_deny */
10039 dummy_permit_deny, /* set_permit_deny */
10040 NULL, /* join_chat */
10041 NULL, /* reject_chat */
10042 NULL, /* get_chat_name */
10043 sipe_chat_invite, /* chat_invite */
10044 sipe_chat_leave, /* chat_leave */
10045 NULL, /* chat_whisper */
10046 sipe_chat_send, /* chat_send */
10047 sipe_keep_alive, /* keepalive */
10048 NULL, /* register_user */
10049 NULL, /* get_cb_info */ // deprecated
10050 NULL, /* get_cb_away */ // deprecated
10051 sipe_alias_buddy, /* alias_buddy */
10052 sipe_group_buddy, /* group_buddy */
10053 sipe_rename_group, /* rename_group */
10054 NULL, /* buddy_free */
10055 sipe_convo_closed, /* convo_closed */
10056 purple_normalize_nocase, /* normalize */
10057 NULL, /* set_buddy_icon */
10058 sipe_remove_group, /* remove_group */
10059 NULL, /* get_cb_real_name */ // TODO?
10060 NULL, /* set_chat_topic */
10061 NULL, /* find_blist_chat */
10062 NULL, /* roomlist_get_list */
10063 NULL, /* roomlist_cancel */
10064 NULL, /* roomlist_expand_category */
10065 NULL, /* can_receive_file */
10066 sipe_ft_send_file, /* send_file */
10067 sipe_ft_new_xfer, /* new_xfer */
10068 NULL, /* offline_message */
10069 NULL, /* whiteboard_prpl_ops */
10070 sipe_send_raw, /* send_raw */
10071 NULL, /* roomlist_room_serialize */
10072 NULL, /* unregister_user */
10073 NULL, /* send_attention */
10074 NULL, /* get_attention_types */
10075 #if !PURPLE_VERSION_CHECK(2,5,0)
10076 /* Backward compatibility when compiling against 2.4.x API */
10077 (void (*)(void)) /* _purple_reserved4 */
10078 #endif
10079 sizeof(PurplePluginProtocolInfo), /* struct_size */
10080 #if PURPLE_VERSION_CHECK(2,5,0)
10081 sipe_get_account_text_table, /* get_account_text_table */
10082 #if PURPLE_VERSION_CHECK(2,6,0)
10083 NULL, /* initiate_media */
10084 NULL, /* get_media_caps */
10085 #endif
10086 #endif
10090 PurplePluginInfo info = {
10091 PURPLE_PLUGIN_MAGIC,
10092 PURPLE_MAJOR_VERSION,
10093 PURPLE_MINOR_VERSION,
10094 PURPLE_PLUGIN_PROTOCOL, /**< type */
10095 NULL, /**< ui_requirement */
10096 0, /**< flags */
10097 NULL, /**< dependencies */
10098 PURPLE_PRIORITY_DEFAULT, /**< priority */
10099 "prpl-sipe", /**< id */
10100 "Office Communicator", /**< name */
10101 PACKAGE_VERSION, /**< version */
10102 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10103 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10104 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10105 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10106 "Gabriel Burt <gburt@novell.com>, " /**< author */
10107 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10108 "pier11 <pier11@operamail.com>", /**< author */
10109 PACKAGE_URL, /**< homepage */
10110 sipe_plugin_load, /**< load */
10111 sipe_plugin_unload, /**< unload */
10112 sipe_plugin_destroy, /**< destroy */
10113 NULL, /**< ui_info */
10114 &prpl_info, /**< extra_info */
10115 NULL,
10116 sipe_actions,
10117 NULL,
10118 NULL,
10119 NULL,
10120 NULL
10124 Local Variables:
10125 mode: c
10126 c-file-style: "bsd"
10127 indent-tabs-mode: t
10128 tab-width: 8
10129 End: