access: added * to tooltip to indicate individual access level
[siplcs.git] / src / core / sipe.c
blob08458b00b05186823f0c8be747a873e10ed5456d
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #ifdef _WIN32
41 #include "win32dep.h" /* for LOCALEDIR */
42 #ifdef _DLL
43 #define _WS2TCPIP_H_
44 #define _WINSOCK2API_
45 #define _LIBC_INTERNAL_
46 #endif /* _DLL */
47 /* for network */
48 #include "libc_interface.h"
49 #else
50 #include <sys/types.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <errno.h>
59 #include <string.h>
60 #include <unistd.h>
62 #include <glib.h>
63 #ifdef HAVE_GMIME
64 #include <gmime/gmime.h>
65 #endif
67 #include "sipe-common.h"
69 #include "account.h"
70 #include "blist.h"
71 #include "connection.h"
72 #include "conversation.h"
73 #include "core.h"
74 #include "circbuffer.h"
75 #include "dnsquery.h"
76 #include "dnssrv.h"
77 #include "ft.h"
78 #include "network.h"
79 #include "notify.h"
80 #include "plugin.h"
81 #include "privacy.h"
82 #include "request.h"
83 #include "savedstatuses.h"
84 #include "sslconn.h"
85 #include "version.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-backend.h"
93 #include "sipe-cal.h"
94 #include "sipe-chat.h"
95 #include "sipe-conf.h"
96 #include "sipe-core.h"
97 #include "sipe-dialog.h"
98 #include "sipe-ews.h"
99 #include "sipe-ft.h"
100 #include "sipe-mime.h"
101 #include "sipe-nls.h"
102 #include "sipe-session.h"
103 #include "sipe-sign.h"
104 #include "sipe-utils.h"
105 #include "sipe-xml.h"
106 #include "http-conn.h"
107 #include "uuid.h"
108 #include "sipe.h"
110 /* Backward compatibility when compiling against 2.4.x API */
111 #if !PURPLE_VERSION_CHECK(2,5,0)
112 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
113 #endif
115 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
117 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
118 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
120 /* Keep in sync with sipe_transport_type! */
121 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
122 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
124 /* Status identifiers (see also: sipe_status_types()) */
125 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
126 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
127 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
128 /* PURPLE_STATUS_UNAVAILABLE: */
129 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
130 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
131 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
132 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
133 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
134 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
135 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
136 /* PURPLE_STATUS_AWAY: */
137 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
138 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
139 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
140 /** Reuters status (user settable) */
141 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
142 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
143 /* ??? PURPLE_STATUS_MOBILE */
144 /* ??? PURPLE_STATUS_TUNE */
146 /* Status attributes (see also sipe_status_types() */
147 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
149 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
151 static struct sipe_activity_map_struct
153 sipe_activity type;
154 const char *token;
155 const char *desc;
156 const char *status_id;
158 } const sipe_activity_map[] =
160 /* This has nothing to do with Availability numbers, like 3500 (online).
161 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
163 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
164 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
165 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
166 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
167 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
168 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
169 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
170 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
171 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
172 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
173 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
174 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
175 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
176 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
177 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
179 /** @param x is sipe_activity */
180 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
183 /* Action name templates */
184 #define ACTION_NAME_PRESENCE "<presence><%s>"
186 static sipe_activity
187 sipe_get_activity_by_token(const char *token)
189 int i;
191 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
193 if (sipe_strequal(token, sipe_activity_map[i].token))
194 return sipe_activity_map[i].type;
197 return sipe_activity_map[0].type;
200 static const char *
201 sipe_get_activity_desc_by_token(const char *token)
203 if (!token) return NULL;
205 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
208 /** Allows to send typed messages from chat window again after account reinstantiation. */
209 static void
210 sipe_rejoin_chat(PurpleConversation *conv)
212 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
213 PURPLE_CONV_CHAT(conv)->left)
215 PURPLE_CONV_CHAT(conv)->left = FALSE;
216 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
220 static char *genbranch()
222 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
223 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
224 rand() & 0xFFFF, rand() & 0xFFFF);
228 static char *default_ua = NULL;
229 static const char*
230 sipe_get_useragent(struct sipe_account_data *sip)
232 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
233 if (is_empty(useragent)) {
234 if (!default_ua) {
235 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
236 /* ref: lzodefs.h */
237 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
238 #define SIPE_TARGET_PLATFORM "linux"
239 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
240 #define SIPE_TARGET_PLATFORM "bsd"
241 #elif defined(__APPLE__) || defined(__MACOS__)
242 #define SIPE_TARGET_PLATFORM "macosx"
243 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
244 #define SIPE_TARGET_PLATFORM "aix"
245 #elif defined(__solaris__) || defined(__sun)
246 #define SIPE_TARGET_PLATFORM "sun"
247 #elif defined(_WIN32)
248 #define SIPE_TARGET_PLATFORM "win"
249 #elif defined(__CYGWIN__)
250 #define SIPE_TARGET_PLATFORM "cygwin"
251 #elif defined(__hpux__)
252 #define SIPE_TARGET_PLATFORM "hpux"
253 #elif defined(__sgi__)
254 #define SIPE_TARGET_PLATFORM "irix"
255 #else
256 #define SIPE_TARGET_PLATFORM "unknown"
257 #endif
259 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
260 #define SIPE_TARGET_ARCH "x86_64"
261 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
262 #define SIPE_TARGET_ARCH "i386"
263 #elif defined(__ppc64__)
264 #define SIPE_TARGET_ARCH "ppc64"
265 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
266 #define SIPE_TARGET_ARCH "ppc"
267 #elif defined(__hppa__) || defined(__hppa)
268 #define SIPE_TARGET_ARCH "hppa"
269 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
270 #define SIPE_TARGET_ARCH "mips"
271 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
272 #define SIPE_TARGET_ARCH "s390"
273 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
274 #define SIPE_TARGET_ARCH "sparc"
275 #elif defined(__arm__)
276 #define SIPE_TARGET_ARCH "arm"
277 #else
278 #define SIPE_TARGET_ARCH "other"
279 #endif
281 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
282 purple_core_get_version(),
283 sip->server_version ? sip->server_version : "");
285 useragent = default_ua;
287 return useragent;
290 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
291 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
293 return "sipe";
296 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
298 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
299 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
300 gpointer data);
302 static void sipe_close(PurpleConnection *gc);
304 static void send_presence_status(struct sipe_account_data *sip);
306 static void sendout_pkt(PurpleConnection *gc, const char *buf);
308 static void sipe_keep_alive(PurpleConnection *gc)
310 struct sipe_account_data *sip = gc->proto_data;
311 if (sip->transport == SIPE_TRANSPORT_UDP) {
312 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
313 gchar buf[2] = {0, 0};
314 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
315 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
316 } else {
317 time_t now = time(NULL);
318 if ((sip->keepalive_timeout > 0) &&
319 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout) &&
320 ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
322 SIPE_DEBUG_INFO("sending keep alive %d", sip->keepalive_timeout);
323 sendout_pkt(gc, "\r\n\r\n");
324 sip->last_keepalive = now;
329 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
331 struct sip_connection *ret = NULL;
332 GSList *entry = sip->openconns;
333 while (entry) {
334 ret = entry->data;
335 if (ret->fd == fd) return ret;
336 entry = entry->next;
338 return NULL;
341 static void sipe_auth_free(struct sip_auth *auth)
343 g_free(auth->opaque);
344 auth->opaque = NULL;
345 g_free(auth->realm);
346 auth->realm = NULL;
347 g_free(auth->target);
348 auth->target = NULL;
349 auth->version = 0;
350 auth->type = AUTH_TYPE_UNSET;
351 auth->retries = 0;
352 auth->expires = 0;
353 g_free(auth->gssapi_data);
354 auth->gssapi_data = NULL;
355 sip_sec_destroy_context(auth->gssapi_context);
356 auth->gssapi_context = NULL;
359 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
361 struct sip_connection *ret = g_new0(struct sip_connection, 1);
362 ret->fd = fd;
363 sip->openconns = g_slist_append(sip->openconns, ret);
364 return ret;
367 static void connection_remove(struct sipe_account_data *sip, int fd)
369 struct sip_connection *conn = connection_find(sip, fd);
370 if (conn) {
371 sip->openconns = g_slist_remove(sip->openconns, conn);
372 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
373 g_free(conn->inbuf);
374 g_free(conn);
378 static void connection_free_all(struct sipe_account_data *sip)
380 struct sip_connection *ret = NULL;
381 GSList *entry = sip->openconns;
382 while (entry) {
383 ret = entry->data;
384 connection_remove(sip, ret->fd);
385 entry = sip->openconns;
389 static void
390 sipe_make_signature(struct sipe_account_data *sip,
391 struct sipmsg *msg);
393 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
395 const char *authuser = sip->authuser;
396 gchar *ret;
398 if (!authuser || strlen(authuser) < 1) {
399 authuser = sip->username;
402 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
403 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
404 gchar *version_str;
406 // If we have a signature for the message, include that
407 if (msg->signature) {
408 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);
411 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
412 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
413 gchar *gssapi_data;
414 gchar *opaque;
415 gchar *sign_str = NULL;
417 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
418 &(auth->expires),
419 auth->type,
420 purple_account_get_bool(sip->account, "sso", TRUE),
421 sip->authdomain ? sip->authdomain : "",
422 authuser,
423 sip->password,
424 auth->target,
425 auth->gssapi_data);
426 if (!gssapi_data || !auth->gssapi_context) {
427 sip->gc->wants_to_die = TRUE;
428 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
429 return NULL;
432 if (auth->version > 3) {
433 sipe_make_signature(sip, msg);
434 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
435 msg->rand, msg->num, msg->signature);
436 } else {
437 sign_str = g_strdup("");
440 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
441 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
442 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);
443 g_free(opaque);
444 g_free(gssapi_data);
445 g_free(version_str);
446 g_free(sign_str);
447 return ret;
450 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
451 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
452 g_free(version_str);
453 return ret;
455 } else { /* Digest */
456 gchar *string;
457 gchar *hex_digest;
458 guchar digest[SIPE_DIGEST_MD5_LENGTH];
460 /* Calculate new session key */
461 if (!auth->opaque) {
462 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
463 if (sip->password) {
465 * Calculate a session key for HTTP MD5 Digest authentation
467 * See RFC 2617 for more information.
469 string = g_strdup_printf("%s:%s:%s",
470 authuser,
471 auth->realm,
472 sip->password);
473 sipe_backend_digest_md5((guchar *)string, strlen(string), digest);
474 g_free(string);
475 auth->opaque = buff_to_hex_str(digest, sizeof(digest));
480 * Calculate a response for HTTP MD5 Digest authentication
482 * See RFC 2617 for more information.
484 string = g_strdup_printf("%s:%s", msg->method, msg->target);
485 sipe_backend_digest_md5((guchar *)string, strlen(string), digest);
486 g_free(string);
488 hex_digest = buff_to_hex_str(digest, sizeof(digest));
489 string = g_strdup_printf("%s:%s:%s", auth->opaque, auth->gssapi_data, hex_digest);
490 g_free(hex_digest);
491 sipe_backend_digest_md5((guchar *)string, strlen(string), digest);
492 g_free(string);
494 hex_digest = buff_to_hex_str(digest, sizeof(digest));
495 SIPE_DEBUG_INFO("Digest response %s", hex_digest);
496 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%08d\", response=\"%s\"", authuser, auth->realm, auth->gssapi_data, msg->target, auth->nc++, hex_digest);
497 g_free(hex_digest);
498 return ret;
502 static char *parse_attribute(const char *attrname, const char *source)
504 const char *tmp, *tmp2;
505 char *retval = NULL;
506 int len = strlen(attrname);
508 if (g_str_has_prefix(source, attrname)) {
509 tmp = source + len;
510 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
511 if (tmp2)
512 retval = g_strndup(tmp, tmp2 - tmp);
513 else
514 retval = g_strdup(tmp);
517 return retval;
520 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
522 int i;
523 gchar **parts;
525 if (!hdr) {
526 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
527 return;
530 if (!g_strncasecmp(hdr, "NTLM", 4)) {
531 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
532 auth->type = AUTH_TYPE_NTLM;
533 hdr += 5;
534 auth->nc = 1;
535 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
536 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
537 auth->type = AUTH_TYPE_KERBEROS;
538 hdr += 9;
539 auth->nc = 3;
540 } else {
541 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
542 auth->type = AUTH_TYPE_DIGEST;
543 hdr += 7;
546 parts = g_strsplit(hdr, "\", ", 0);
547 for (i = 0; parts[i]; i++) {
548 char *tmp;
550 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
552 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
553 g_free(auth->gssapi_data);
554 auth->gssapi_data = tmp;
556 if (auth->type == AUTH_TYPE_NTLM) {
557 /* NTLM module extracts nonce from gssapi-data */
558 auth->nc = 3;
561 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
562 /* Only used with AUTH_TYPE_DIGEST */
563 g_free(auth->gssapi_data);
564 auth->gssapi_data = tmp;
565 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
566 g_free(auth->opaque);
567 auth->opaque = tmp;
568 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
569 g_free(auth->realm);
570 auth->realm = tmp;
572 if (auth->type == AUTH_TYPE_DIGEST) {
573 /* Throw away old session key */
574 g_free(auth->opaque);
575 auth->opaque = NULL;
576 auth->nc = 1;
578 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
579 g_free(auth->target);
580 auth->target = tmp;
581 } else if ((tmp = parse_attribute("version=", parts[i]))) {
582 auth->version = atoi(tmp);
583 g_free(tmp);
585 // uncomment to revert to previous functionality if version 3+ does not work.
586 // auth->version = 2;
588 g_strfreev(parts);
590 return;
593 static void sipe_canwrite_cb(gpointer data,
594 SIPE_UNUSED_PARAMETER gint source,
595 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
597 PurpleConnection *gc = data;
598 struct sipe_account_data *sip = gc->proto_data;
599 gsize max_write;
600 gssize written;
602 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
604 if (max_write == 0) {
605 if (sip->tx_handler != 0){
606 purple_input_remove(sip->tx_handler);
607 sip->tx_handler = 0;
609 return;
612 written = write(sip->fd, sip->txbuf->outptr, max_write);
614 if (written < 0 && errno == EAGAIN)
615 written = 0;
616 else if (written <= 0) {
617 /*TODO: do we really want to disconnect on a failure to write?*/
618 purple_connection_error(gc, _("Could not write"));
619 return;
622 purple_circ_buffer_mark_read(sip->txbuf, written);
625 static void sipe_canwrite_cb_ssl(gpointer data,
626 SIPE_UNUSED_PARAMETER gint src,
627 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
629 PurpleConnection *gc = data;
630 struct sipe_account_data *sip = gc->proto_data;
631 gsize max_write;
632 gssize written;
634 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
636 if (max_write == 0) {
637 if (sip->tx_handler != 0) {
638 purple_input_remove(sip->tx_handler);
639 sip->tx_handler = 0;
640 return;
644 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
646 if (written < 0 && errno == EAGAIN)
647 written = 0;
648 else if (written <= 0) {
649 /*TODO: do we really want to disconnect on a failure to write?*/
650 purple_connection_error(gc, _("Could not write"));
651 return;
654 purple_circ_buffer_mark_read(sip->txbuf, written);
657 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
659 static void send_later_cb(gpointer data, gint source,
660 SIPE_UNUSED_PARAMETER const gchar *error)
662 PurpleConnection *gc = data;
663 struct sipe_account_data *sip;
664 struct sip_connection *conn;
666 if (!PURPLE_CONNECTION_IS_VALID(gc))
668 if (source >= 0)
669 close(source);
670 return;
673 if (source < 0) {
674 purple_connection_error(gc, _("Could not connect"));
675 return;
678 sip = gc->proto_data;
679 sip->fd = source;
680 sip->connecting = FALSE;
681 sip->last_keepalive = time(NULL);
683 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
685 /* If there is more to write now, we need to register a handler */
686 if (sip->txbuf->bufused > 0)
687 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
689 conn = connection_create(sip, source);
690 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
693 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
695 struct sipe_account_data *sip;
697 if (!PURPLE_CONNECTION_IS_VALID(gc))
699 if (gsc) purple_ssl_close(gsc);
700 return NULL;
703 sip = gc->proto_data;
704 sip->fd = gsc->fd;
705 sip->gsc = gsc;
706 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
707 sip->connecting = FALSE;
708 sip->last_keepalive = time(NULL);
710 connection_create(sip, gsc->fd);
712 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
714 return sip;
717 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
718 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
720 PurpleConnection *gc = data;
721 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
722 if (sip == NULL) return;
724 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
726 /* If there is more to write now */
727 if (sip->txbuf->bufused > 0) {
728 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
733 static void sendlater(PurpleConnection *gc, const char *buf)
735 struct sipe_account_data *sip = gc->proto_data;
737 if (!sip->connecting) {
738 SIPE_DEBUG_INFO("connecting to %s port %d", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
739 if (sip->transport == SIPE_TRANSPORT_TLS){
740 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
741 } else {
742 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
743 purple_connection_error(gc, _("Could not create socket"));
746 sip->connecting = TRUE;
749 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
750 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
752 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
755 static void sendout_pkt(PurpleConnection *gc, const char *buf)
757 struct sipe_account_data *sip = gc->proto_data;
758 time_t currtime = time(NULL);
759 int writelen = strlen(buf);
760 char *tmp;
762 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime), tmp = fix_newlines(buf));
763 g_free(tmp);
764 if (sip->transport == SIPE_TRANSPORT_UDP) {
765 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
766 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
768 } else {
769 int ret;
770 if (sip->fd < 0) {
771 sendlater(gc, buf);
772 return;
775 if (sip->tx_handler) {
776 ret = -1;
777 errno = EAGAIN;
778 } else{
779 if (sip->gsc){
780 ret = purple_ssl_write(sip->gsc, buf, writelen);
781 }else{
782 ret = write(sip->fd, buf, writelen);
786 if (ret < 0 && errno == EAGAIN)
787 ret = 0;
788 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
789 sendlater(gc, buf);
790 return;
793 if (ret < writelen) {
794 if (!sip->tx_handler){
795 if (sip->gsc){
796 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
798 else{
799 sip->tx_handler = purple_input_add(sip->fd,
800 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
801 gc);
805 /* XXX: is it OK to do this? You might get part of a request sent
806 with part of another. */
807 if (sip->txbuf->bufused > 0)
808 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
810 purple_circ_buffer_append(sip->txbuf, buf + ret,
811 writelen - ret);
816 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
818 sendout_pkt(gc, buf);
819 return len;
822 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
824 GSList *tmp = msg->headers;
825 gchar *name;
826 gchar *value;
827 GString *outstr = g_string_new("");
828 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
829 while (tmp) {
830 name = ((struct sipnameval*) (tmp->data))->name;
831 value = ((struct sipnameval*) (tmp->data))->value;
832 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
833 tmp = g_slist_next(tmp);
835 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
836 sendout_pkt(sip->gc, outstr->str);
837 g_string_free(outstr, TRUE);
840 static void
841 sipe_make_signature(struct sipe_account_data *sip,
842 struct sipmsg *msg)
844 if (sip->registrar.gssapi_context) {
845 struct sipmsg_breakdown msgbd;
846 gchar *signature_input_str;
847 msgbd.msg = msg;
848 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
849 msgbd.rand = g_strdup_printf("%08x", g_random_int());
850 sip->registrar.ntlm_num++;
851 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
852 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
853 if (signature_input_str != NULL) {
854 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
855 msg->signature = signature_hex;
856 msg->rand = g_strdup(msgbd.rand);
857 msg->num = g_strdup(msgbd.num);
858 g_free(signature_input_str);
860 sipmsg_breakdown_free(&msgbd);
864 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
866 gchar * buf;
868 if (sip->registrar.type == AUTH_TYPE_UNSET) {
869 return;
872 sipe_make_signature(sip, msg);
874 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
875 buf = auth_header(sip, &sip->registrar, msg);
876 if (buf) {
877 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
879 g_free(buf);
880 } 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")) {
881 sip->registrar.nc = 3;
882 sip->registrar.type = AUTH_TYPE_NTLM;
883 #ifdef HAVE_LIBKRB5
884 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
885 sip->registrar.type = AUTH_TYPE_KERBEROS;
887 #endif
890 buf = auth_header(sip, &sip->registrar, msg);
891 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
892 g_free(buf);
893 } else {
894 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
898 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
899 const char *text, const char *body)
901 gchar *name;
902 gchar *value;
903 GString *outstr = g_string_new("");
904 struct sipe_account_data *sip = gc->proto_data;
905 gchar *contact;
906 GSList *tmp;
907 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
909 /* Can return NULL! */
910 contact = get_contact(sip);
911 if (contact) {
912 sipmsg_add_header(msg, "Contact", contact);
913 g_free(contact);
916 if (body) {
917 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
918 sipmsg_add_header(msg, "Content-Length", len);
919 g_free(len);
920 } else {
921 sipmsg_add_header(msg, "Content-Length", "0");
924 msg->response = code;
926 sipmsg_strip_headers(msg, keepers);
927 sipmsg_merge_new_headers(msg);
928 sign_outgoing_message(msg, sip, msg->method);
930 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
931 tmp = msg->headers;
932 while (tmp) {
933 name = ((struct sipnameval*) (tmp->data))->name;
934 value = ((struct sipnameval*) (tmp->data))->value;
936 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
937 tmp = g_slist_next(tmp);
939 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
940 sendout_pkt(gc, outstr->str);
941 g_string_free(outstr, TRUE);
944 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
946 if (sip->transactions) {
947 sip->transactions = g_slist_remove(sip->transactions, trans);
948 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
950 if (trans->msg) sipmsg_free(trans->msg);
951 if (trans->payload) {
952 (*trans->payload->destroy)(trans->payload->data);
953 g_free(trans->payload);
955 g_free(trans->key);
956 g_free(trans);
960 static struct transaction *
961 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
963 const gchar *call_id;
964 const gchar *cseq;
965 struct transaction *trans = g_new0(struct transaction, 1);
967 trans->time = time(NULL);
968 trans->msg = (struct sipmsg *)msg;
969 call_id = sipmsg_find_header(trans->msg, "Call-ID");
970 cseq = sipmsg_find_header(trans->msg, "CSeq");
971 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
972 trans->callback = callback;
973 sip->transactions = g_slist_append(sip->transactions, trans);
974 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
975 return trans;
978 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
980 struct transaction *trans;
981 GSList *transactions = sip->transactions;
982 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
983 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
984 gchar *key;
986 if (!call_id || !cseq) {
987 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
988 return NULL;
991 key = g_strdup_printf("<%s><%s>", call_id, cseq);
992 while (transactions) {
993 trans = transactions->data;
994 if (!g_strcasecmp(trans->key, key)) {
995 g_free(key);
996 return trans;
998 transactions = transactions->next;
1001 g_free(key);
1002 return NULL;
1005 struct transaction *
1006 send_sip_request(PurpleConnection *gc, const gchar *method,
1007 const gchar *url, const gchar *to, const gchar *addheaders,
1008 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
1010 struct sipe_account_data *sip = gc->proto_data;
1011 const char *addh = "";
1012 char *buf;
1013 struct sipmsg *msg;
1014 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
1015 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
1016 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
1017 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
1018 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
1019 gchar *route = g_strdup("");
1020 gchar *epid = get_epid(sip);
1021 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
1022 struct transaction *trans = NULL;
1024 if (dialog && dialog->routes)
1026 GSList *iter = dialog->routes;
1028 while(iter)
1030 char *tmp = route;
1031 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
1032 g_free(tmp);
1033 iter = g_slist_next(iter);
1037 if (!ourtag && !dialog) {
1038 ourtag = gentag();
1041 if (sipe_strequal(method, "REGISTER")) {
1042 if (sip->regcallid) {
1043 g_free(callid);
1044 callid = g_strdup(sip->regcallid);
1045 } else {
1046 sip->regcallid = g_strdup(callid);
1048 cseq = ++sip->cseq;
1051 if (addheaders) addh = addheaders;
1053 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
1054 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1055 "From: <sip:%s>%s%s;epid=%s\r\n"
1056 "To: <%s>%s%s%s%s\r\n"
1057 "Max-Forwards: 70\r\n"
1058 "CSeq: %d %s\r\n"
1059 "User-Agent: %s\r\n"
1060 "Call-ID: %s\r\n"
1061 "%s%s"
1062 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
1063 method,
1064 dialog && dialog->request ? dialog->request : url,
1065 TRANSPORT_DESCRIPTOR,
1066 sipe_backend_network_ip_address(),
1067 sip->listenport,
1068 branch ? ";branch=" : "",
1069 branch ? branch : "",
1070 sip->username,
1071 ourtag ? ";tag=" : "",
1072 ourtag ? ourtag : "",
1073 epid,
1075 theirtag ? ";tag=" : "",
1076 theirtag ? theirtag : "",
1077 theirepid ? ";epid=" : "",
1078 theirepid ? theirepid : "",
1079 cseq,
1080 method,
1081 sipe_get_useragent(sip),
1082 callid,
1083 route,
1084 addh,
1085 body ? (gsize) strlen(body) : 0,
1086 body ? body : "");
1089 //printf ("parsing msg buf:\n%s\n\n", buf);
1090 msg = sipmsg_parse_msg(buf);
1092 g_free(buf);
1093 g_free(ourtag);
1094 g_free(theirtag);
1095 g_free(theirepid);
1096 g_free(branch);
1097 g_free(callid);
1098 g_free(route);
1099 g_free(epid);
1101 sign_outgoing_message (msg, sip, method);
1103 buf = sipmsg_to_string (msg);
1105 /* add to ongoing transactions */
1106 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1107 if (!sipe_strequal(method, "ACK")) {
1108 trans = transactions_add_buf(sip, msg, tc);
1109 } else {
1110 sipmsg_free(msg);
1112 sendout_pkt(gc, buf);
1113 g_free(buf);
1115 return trans;
1119 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1121 static void
1122 send_soap_request_with_cb(struct sipe_account_data *sip,
1123 gchar *from0,
1124 gchar *body,
1125 TransCallback callback,
1126 struct transaction_payload *payload)
1128 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
1129 gchar *contact = get_contact(sip);
1130 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1131 "Content-Type: application/SOAP+xml\r\n",contact);
1133 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1134 trans->payload = payload;
1136 g_free(from);
1137 g_free(contact);
1138 g_free(hdr);
1141 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1143 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
1146 static char *get_contact_register(struct sipe_account_data *sip)
1148 char *epid = get_epid(sip);
1149 char *uuid = generateUUIDfromEPID(epid);
1150 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>\"", sipe_backend_network_ip_address(), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1151 g_free(uuid);
1152 g_free(epid);
1153 return(buf);
1156 static void do_register_exp(struct sipe_account_data *sip, int expire)
1158 char *uri;
1159 char *expires;
1160 char *to;
1161 char *contact;
1162 char *hdr;
1164 if (!sip->sipdomain) return;
1166 uri = sip_uri_from_name(sip->sipdomain);
1167 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1168 to = sip_uri_self(sip);
1169 contact = get_contact_register(sip);
1170 hdr = g_strdup_printf("Contact: %s\r\n"
1171 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1172 "Event: registration\r\n"
1173 "Allow-Events: presence\r\n"
1174 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1175 "%s", contact, expires);
1176 g_free(contact);
1177 g_free(expires);
1179 sip->registerstatus = 1;
1181 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1182 process_register_response);
1184 g_free(hdr);
1185 g_free(uri);
1186 g_free(to);
1189 static void do_register_cb(struct sipe_account_data *sip,
1190 SIPE_UNUSED_PARAMETER void *unused)
1192 do_register_exp(sip, -1);
1193 sip->reregister_set = FALSE;
1196 static void do_register(struct sipe_account_data *sip)
1198 do_register_exp(sip, -1);
1202 * Returns pointer to URI without sip: prefix if any
1204 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
1205 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
1207 * Doesn't allocate memory
1209 static const char *
1210 sipe_get_no_sip_uri(const char *sip_uri)
1212 const char *prefix = "sip:";
1213 if (!sip_uri) return NULL;
1215 if (g_str_has_prefix(sip_uri, prefix)) {
1216 return (sip_uri+strlen(prefix));
1217 } else {
1218 return sip_uri;
1222 static void
1223 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1225 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1226 send_soap_request(sip, body);
1227 g_free(body);
1230 static void
1231 sipe_change_access_level(struct sipe_account_data *sip,
1232 const int container_id,
1233 const gchar *type,
1234 const gchar *value);
1236 static void
1237 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1239 if (allow) {
1240 SIPE_DEBUG_INFO("Authorizing contact %s", who);
1241 } else {
1242 SIPE_DEBUG_INFO("Blocking contact %s", who);
1245 if (sip->ocs2007) {
1246 sipe_change_access_level(sip, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
1247 } else {
1248 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1252 static
1253 void sipe_auth_user_cb(void * data)
1255 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1256 if (!job) return;
1258 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1259 g_free(job);
1262 static
1263 void sipe_deny_user_cb(void * data)
1265 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1266 if (!job) return;
1268 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1269 g_free(job);
1272 static void
1273 sipe_add_permit(PurpleConnection *gc, const char *name)
1275 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1277 sipe_contact_allow_deny(sip, name, TRUE);
1280 static void
1281 sipe_add_deny(PurpleConnection *gc, const char *name)
1283 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1284 sipe_contact_allow_deny(sip, name, FALSE);
1287 /*static void
1288 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1290 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1291 sipe_contact_set_acl(sip, name, "");
1294 /** @applicable: 2005-
1296 static void
1297 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1299 sipe_xml *watchers;
1300 const sipe_xml *watcher;
1301 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1302 if (msg->response != 0 && msg->response != 200) return;
1304 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1306 watchers = sipe_xml_parse(msg->body, msg->bodylen);
1307 if (!watchers) return;
1309 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
1310 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
1311 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
1312 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1314 // TODO pull out optional displayName to pass as alias
1315 if (remote_user) {
1316 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1317 job->who = remote_user;
1318 job->sip = sip;
1319 purple_account_request_authorization(
1320 sip->account,
1321 remote_user,
1322 _("you"), /* id */
1323 alias,
1324 NULL, /* message */
1325 on_list,
1326 sipe_auth_user_cb,
1327 sipe_deny_user_cb,
1328 (void *) job);
1333 sipe_xml_free(watchers);
1334 return;
1337 static void
1338 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1340 PurpleGroup * purple_group = purple_find_group(group->name);
1341 if (!purple_group) {
1342 purple_group = purple_group_new(group->name);
1343 purple_blist_add_group(purple_group, NULL);
1346 if (purple_group) {
1347 group->purple_group = purple_group;
1348 sip->groups = g_slist_append(sip->groups, group);
1349 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1350 } else {
1351 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1355 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1357 struct sipe_group *group;
1358 GSList *entry;
1359 if (sip == NULL) {
1360 return NULL;
1363 entry = sip->groups;
1364 while (entry) {
1365 group = entry->data;
1366 if (group->id == id) {
1367 return group;
1369 entry = entry->next;
1371 return NULL;
1374 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1376 struct sipe_group *group;
1377 GSList *entry;
1378 if (!sip || !name) {
1379 return NULL;
1382 entry = sip->groups;
1383 while (entry) {
1384 group = entry->data;
1385 if (sipe_strequal(group->name, name)) {
1386 return group;
1388 entry = entry->next;
1390 return NULL;
1393 static void
1394 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1396 gchar *body;
1397 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1398 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1399 send_soap_request(sip, body);
1400 g_free(body);
1401 g_free(group->name);
1402 group->name = g_strdup(name);
1406 * Only appends if no such value already stored.
1407 * Like Set in Java.
1409 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1410 GSList * res = list;
1411 if (!g_slist_find_custom(list, data, func)) {
1412 res = g_slist_insert_sorted(list, data, func);
1414 return res;
1417 static int
1418 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1419 return group1->id - group2->id;
1423 * Returns string like "2 4 7 8" - group ids buddy belong to.
1425 static gchar *
1426 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1427 int i = 0;
1428 gchar *res;
1429 //creating array from GList, converting int to gchar*
1430 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1431 GSList *entry = buddy->groups;
1433 if (!ids_arr) return NULL;
1435 while (entry) {
1436 struct sipe_group * group = entry->data;
1437 ids_arr[i] = g_strdup_printf("%d", group->id);
1438 entry = entry->next;
1439 i++;
1441 ids_arr[i] = NULL;
1442 res = g_strjoinv(" ", ids_arr);
1443 g_strfreev(ids_arr);
1444 return res;
1448 * Sends buddy update to server
1450 static void
1451 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1453 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1454 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1456 if (buddy && purple_buddy) {
1457 const char *alias = purple_buddy_get_alias(purple_buddy);
1458 gchar *groups = sipe_get_buddy_groups_string(buddy);
1459 if (groups) {
1460 gchar *body;
1461 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1463 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1464 alias, groups, "true", buddy->name, sip->contacts_delta++
1466 send_soap_request(sip, body);
1467 g_free(groups);
1468 g_free(body);
1473 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1475 if (msg->response == 200) {
1476 struct sipe_group *group;
1477 struct group_user_context *ctx = trans->payload->data;
1478 sipe_xml *xml;
1479 const sipe_xml *node;
1480 char *group_id;
1481 struct sipe_buddy *buddy;
1483 xml = sipe_xml_parse(msg->body, msg->bodylen);
1484 if (!xml) {
1485 return FALSE;
1488 node = sipe_xml_child(xml, "Body/addGroup/groupID");
1489 if (!node) {
1490 sipe_xml_free(xml);
1491 return FALSE;
1494 group_id = sipe_xml_data(node);
1495 if (!group_id) {
1496 sipe_xml_free(xml);
1497 return FALSE;
1500 group = g_new0(struct sipe_group, 1);
1501 group->id = (int)g_ascii_strtod(group_id, NULL);
1502 g_free(group_id);
1503 group->name = g_strdup(ctx->group_name);
1505 sipe_group_add(sip, group);
1507 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1508 if (buddy) {
1509 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1512 sipe_group_set_user(sip, ctx->user_name);
1514 sipe_xml_free(xml);
1515 return TRUE;
1517 return FALSE;
1520 static void sipe_group_context_destroy(gpointer data)
1522 struct group_user_context *ctx = data;
1523 g_free(ctx->group_name);
1524 g_free(ctx->user_name);
1525 g_free(ctx);
1528 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1530 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1531 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1532 gchar *body;
1533 ctx->group_name = g_strdup(name);
1534 ctx->user_name = g_strdup(who);
1535 payload->destroy = sipe_group_context_destroy;
1536 payload->data = ctx;
1538 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1539 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1540 g_free(body);
1544 * Data structure for scheduled actions
1547 struct scheduled_action {
1549 * Name of action.
1550 * Format is <Event>[<Data>...]
1551 * Example: <presence><sip:user@domain.com> or <registration>
1553 gchar *name;
1554 guint timeout_handler;
1555 gboolean repetitive;
1556 Action action;
1557 GDestroyNotify destroy;
1558 struct sipe_account_data *sip;
1559 void *payload;
1563 * A timer callback
1564 * Should return FALSE if repetitive action is not needed
1566 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1568 gboolean ret;
1569 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1570 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1571 SIPE_DEBUG_INFO("sip->timeouts count:%d after removal", g_slist_length(sched_action->sip->timeouts));
1572 (sched_action->action)(sched_action->sip, sched_action->payload);
1573 ret = sched_action->repetitive;
1574 if (sched_action->destroy) {
1575 (*sched_action->destroy)(sched_action->payload);
1577 g_free(sched_action->name);
1578 g_free(sched_action);
1579 return ret;
1583 * Kills action timer effectively cancelling
1584 * scheduled action
1586 * @param name of action
1588 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1590 GSList *entry;
1592 if (!sip->timeouts || !name) return;
1594 entry = sip->timeouts;
1595 while (entry) {
1596 struct scheduled_action *sched_action = entry->data;
1597 if(sipe_strequal(sched_action->name, name)) {
1598 GSList *to_delete = entry;
1599 entry = entry->next;
1600 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1601 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1602 purple_timeout_remove(sched_action->timeout_handler);
1603 if (sched_action->destroy) {
1604 (*sched_action->destroy)(sched_action->payload);
1606 g_free(sched_action->name);
1607 g_free(sched_action);
1608 } else {
1609 entry = entry->next;
1614 static void
1615 sipe_schedule_action0(const gchar *name,
1616 int timeout,
1617 gboolean isSeconds,
1618 Action action,
1619 GDestroyNotify destroy,
1620 struct sipe_account_data *sip,
1621 void *payload)
1623 struct scheduled_action *sched_action;
1625 /* Make sure each action only exists once */
1626 sipe_cancel_scheduled_action(sip, name);
1628 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1629 sched_action = g_new0(struct scheduled_action, 1);
1630 sched_action->repetitive = FALSE;
1631 sched_action->name = g_strdup(name);
1632 sched_action->action = action;
1633 sched_action->destroy = destroy;
1634 sched_action->sip = sip;
1635 sched_action->payload = payload;
1636 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1637 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1638 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1639 SIPE_DEBUG_INFO("sip->timeouts count:%d after addition", g_slist_length(sip->timeouts));
1642 void
1643 sipe_schedule_action(const gchar *name,
1644 int timeout,
1645 Action action,
1646 GDestroyNotify destroy,
1647 struct sipe_account_data *sip,
1648 void *payload)
1650 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1654 * Same as sipe_schedule_action() but timeout is in milliseconds.
1656 static void
1657 sipe_schedule_action_msec(const gchar *name,
1658 int timeout,
1659 Action action,
1660 GDestroyNotify destroy,
1661 struct sipe_account_data *sip,
1662 void *payload)
1664 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1667 static void
1668 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1669 time_t calculate_from);
1671 static int
1672 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1674 static const char*
1675 sipe_get_status_by_availability(int avail,
1676 char** activity);
1678 static void
1679 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1680 const char *status_id,
1681 const char *message,
1682 time_t do_not_publish[]);
1684 static void
1685 sipe_apply_calendar_status(struct sipe_account_data *sip,
1686 struct sipe_buddy *sbuddy,
1687 const char *status_id)
1689 time_t cal_avail_since;
1690 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1691 int avail;
1692 gchar *self_uri;
1694 if (!sbuddy) return;
1696 if (cal_status < SIPE_CAL_NO_DATA) {
1697 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1698 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1701 /* scheduled Cal update call */
1702 if (!status_id) {
1703 status_id = sbuddy->last_non_cal_status_id;
1704 g_free(sbuddy->activity);
1705 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1708 if (!status_id) {
1709 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1710 sbuddy->name ? sbuddy->name : "" );
1711 return;
1714 /* adjust to calendar status */
1715 if (cal_status != SIPE_CAL_NO_DATA) {
1716 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1718 if (cal_status == SIPE_CAL_BUSY
1719 && cal_avail_since > sbuddy->user_avail_since
1720 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1722 status_id = SIPE_STATUS_ID_BUSY;
1723 g_free(sbuddy->activity);
1724 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1726 avail = sipe_get_availability_by_status(status_id, NULL);
1728 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1729 if (cal_avail_since > sbuddy->activity_since) {
1730 if (cal_status == SIPE_CAL_OOF
1731 && avail >= 15000) /* 12000 in 2007 */
1733 g_free(sbuddy->activity);
1734 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1739 /* then set status_id actually */
1740 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1741 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1743 /* set our account state to the one in roaming (including calendar info) */
1744 self_uri = sip_uri_self(sip);
1745 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1746 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1747 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1750 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1751 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1753 g_free(self_uri);
1756 static void
1757 sipe_got_user_status(struct sipe_account_data *sip,
1758 const char* uri,
1759 const char *status_id)
1761 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
1763 if (!sbuddy) return;
1765 /* Check if on 2005 system contact's calendar,
1766 * then set/preserve it.
1768 if (!sip->ocs2007) {
1769 sipe_apply_calendar_status(sip, sbuddy, status_id);
1770 } else {
1771 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1775 static void
1776 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1777 struct sipe_buddy *sbuddy,
1778 struct sipe_account_data *sip)
1780 sipe_apply_calendar_status(sip, sbuddy, NULL);
1784 * Updates contact's status
1785 * based on their calendar information.
1787 * Applicability: 2005 systems
1789 static void
1790 update_calendar_status(struct sipe_account_data *sip)
1792 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1793 g_hash_table_foreach(sip->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1795 /* repeat scheduling */
1796 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1800 * Schedules process of contacts' status update
1801 * based on their calendar information.
1802 * Should be scheduled to the beginning of every
1803 * 15 min interval, like:
1804 * 13:00, 13:15, 13:30, 13:45, etc.
1806 * Applicability: 2005 systems
1808 static void
1809 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1810 time_t calculate_from)
1812 int interval = 15*60;
1813 /** start of the beginning of closest 15 min interval. */
1814 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1816 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1817 asctime(localtime(&calculate_from)));
1818 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1819 asctime(localtime(&next_start)));
1821 sipe_schedule_action("<+2005-cal-status>",
1822 (int)(next_start - time(NULL)),
1823 (Action)update_calendar_status,
1824 NULL,
1825 sip,
1826 NULL);
1830 * Schedules process of self status publish
1831 * based on own calendar information.
1832 * Should be scheduled to the beginning of every
1833 * 15 min interval, like:
1834 * 13:00, 13:15, 13:30, 13:45, etc.
1836 * Applicability: 2007+ systems
1838 static void
1839 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1840 time_t calculate_from)
1842 int interval = 5*60;
1843 /** start of the beginning of closest 5 min interval. */
1844 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1846 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1847 asctime(localtime(&calculate_from)));
1848 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1849 asctime(localtime(&next_start)));
1851 sipe_schedule_action("<+2007-cal-status>",
1852 (int)(next_start - time(NULL)),
1853 (Action)publish_calendar_status_self,
1854 NULL,
1855 sip,
1856 NULL);
1859 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1861 /** Should be g_free()'d
1863 static gchar *
1864 sipe_get_subscription_key(const gchar *event,
1865 const gchar *with)
1867 gchar *key = NULL;
1869 if (is_empty(event)) return NULL;
1871 if (event && sipe_strcase_equal(event, "presence")) {
1872 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1873 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1875 /* @TODO drop participated buddies' just_added flag */
1876 } else if (event) {
1877 /* Subscription is identified by <event> key */
1878 key = g_strdup_printf("<%s>", event);
1881 return key;
1884 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1885 SIPE_UNUSED_PARAMETER struct transaction *trans)
1887 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1888 const gchar *event = sipmsg_find_header(msg, "Event");
1889 gchar *key;
1891 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1892 if (!event) {
1893 struct sipmsg *request_msg = trans->msg;
1894 event = sipmsg_find_header(request_msg, "Event");
1897 key = sipe_get_subscription_key(event, with);
1899 /* 200 OK; 481 Call Leg Does Not Exist */
1900 if (key && (msg->response == 200 || msg->response == 481)) {
1901 if (g_hash_table_lookup(sip->subscriptions, key)) {
1902 g_hash_table_remove(sip->subscriptions, key);
1903 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1907 /* create/store subscription dialog if not yet */
1908 if (msg->response == 200) {
1909 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1910 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1912 if (key) {
1913 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1914 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1916 subscription->dialog.callid = g_strdup(callid);
1917 subscription->dialog.cseq = atoi(cseq);
1918 subscription->dialog.with = g_strdup(with);
1919 subscription->event = g_strdup(event);
1920 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1922 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1925 g_free(cseq);
1928 g_free(key);
1929 g_free(with);
1931 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1933 process_incoming_notify(sip, msg, FALSE, FALSE);
1935 return TRUE;
1938 static void sipe_subscribe_resource_uri(const char *name,
1939 SIPE_UNUSED_PARAMETER gpointer value,
1940 gchar **resources_uri)
1942 gchar *tmp = *resources_uri;
1943 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1944 g_free(tmp);
1947 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1949 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1950 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1951 gchar *tmp = *resources_uri;
1953 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1955 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1956 g_free(tmp);
1960 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1961 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1962 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1963 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1964 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1967 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1969 gchar *key;
1970 gchar *contact = get_contact(sip);
1971 gchar *request;
1972 gchar *content;
1973 gchar *require = "";
1974 gchar *accept = "";
1975 gchar *autoextend = "";
1976 gchar *content_type;
1977 struct sip_dialog *dialog;
1979 if (sip->ocs2007) {
1980 require = ", categoryList";
1981 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1982 content_type = "application/msrtc-adrl-categorylist+xml";
1983 content = g_strdup_printf(
1984 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1985 "<action name=\"subscribe\" id=\"63792024\">\n"
1986 "<adhocList>\n%s</adhocList>\n"
1987 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1988 "<category name=\"calendarData\"/>\n"
1989 "<category name=\"contactCard\"/>\n"
1990 "<category name=\"note\"/>\n"
1991 "<category name=\"state\"/>\n"
1992 "</categoryList>\n"
1993 "</action>\n"
1994 "</batchSub>", sip->username, resources_uri);
1995 } else {
1996 autoextend = "Supported: com.microsoft.autoextend\r\n";
1997 content_type = "application/adrl+xml";
1998 content = g_strdup_printf(
1999 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
2000 "<create xmlns=\"\">\n%s</create>\n"
2001 "</adhoclist>\n", sip->username, sip->username, resources_uri);
2003 g_free(resources_uri);
2005 request = g_strdup_printf(
2006 "Require: adhoclist%s\r\n"
2007 "Supported: eventlist\r\n"
2008 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
2009 "Supported: ms-piggyback-first-notify\r\n"
2010 "%sSupported: ms-benotify\r\n"
2011 "Proxy-Require: ms-benotify\r\n"
2012 "Event: presence\r\n"
2013 "Content-Type: %s\r\n"
2014 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
2015 g_free(contact);
2017 /* subscribe to buddy presence */
2018 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2019 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2020 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2021 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2023 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2025 g_free(content);
2026 g_free(to);
2027 g_free(request);
2028 g_free(key);
2031 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
2032 SIPE_UNUSED_PARAMETER void *unused)
2034 gchar *to = sip_uri_self(sip);
2035 gchar *resources_uri = g_strdup("");
2036 if (sip->ocs2007) {
2037 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
2038 } else {
2039 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
2042 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
2045 struct presence_batched_routed {
2046 gchar *host;
2047 GSList *buddies;
2050 static void sipe_subscribe_presence_batched_routed_free(void *payload)
2052 struct presence_batched_routed *data = payload;
2053 GSList *buddies = data->buddies;
2054 while (buddies) {
2055 g_free(buddies->data);
2056 buddies = buddies->next;
2058 g_slist_free(data->buddies);
2059 g_free(data->host);
2060 g_free(payload);
2063 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
2065 struct presence_batched_routed *data = payload;
2066 GSList *buddies = data->buddies;
2067 gchar *resources_uri = g_strdup("");
2068 while (buddies) {
2069 gchar *tmp = resources_uri;
2070 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
2071 g_free(tmp);
2072 buddies = buddies->next;
2074 sipe_subscribe_presence_batched_to(sip, resources_uri,
2075 g_strdup(data->host));
2079 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2080 * The user sends a single SUBSCRIBE request to the subscribed contact.
2081 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2085 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
2088 gchar *key;
2089 gchar *to = sip_uri((char *)buddy_name);
2090 gchar *tmp = get_contact(sip);
2091 gchar *request;
2092 gchar *content = NULL;
2093 gchar *autoextend = "";
2094 gchar *content_type = "";
2095 struct sip_dialog *dialog;
2096 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
2097 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
2099 if (sbuddy) sbuddy->just_added = FALSE;
2101 if (sip->ocs2007) {
2102 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2103 } else {
2104 autoextend = "Supported: com.microsoft.autoextend\r\n";
2107 request = g_strdup_printf(
2108 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2109 "Supported: ms-piggyback-first-notify\r\n"
2110 "%s%sSupported: ms-benotify\r\n"
2111 "Proxy-Require: ms-benotify\r\n"
2112 "Event: presence\r\n"
2113 "Contact: %s\r\n", autoextend, content_type, tmp);
2115 if (sip->ocs2007) {
2116 content = g_strdup_printf(
2117 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2118 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2119 "<resource uri=\"%s\"%s\n"
2120 "</adhocList>\n"
2121 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2122 "<category name=\"calendarData\"/>\n"
2123 "<category name=\"contactCard\"/>\n"
2124 "<category name=\"note\"/>\n"
2125 "<category name=\"state\"/>\n"
2126 "</categoryList>\n"
2127 "</action>\n"
2128 "</batchSub>", sip->username, to, context);
2131 g_free(tmp);
2133 /* subscribe to buddy presence */
2134 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2135 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
2136 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2137 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2139 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
2141 g_free(content);
2142 g_free(to);
2143 g_free(request);
2144 g_free(key);
2147 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
2149 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
2151 if (!purple_status_is_active(status))
2152 return;
2154 if (account->gc) {
2155 struct sipe_account_data *sip = account->gc->proto_data;
2157 if (sip) {
2158 gchar *action_name;
2159 gchar *tmp;
2160 time_t now = time(NULL);
2161 const char *status_id = purple_status_get_id(status);
2162 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
2163 sipe_activity activity = sipe_get_activity_by_token(status_id);
2164 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
2166 /* when other point of presence clears note, but we are keeping
2167 * state if OOF note.
2169 if (do_not_publish && !note && sip->ews && sip->ews->oof_note) {
2170 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2171 do_not_publish = FALSE;
2174 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2175 status_id, (int)sip->do_not_publish[activity], (int)now);
2177 sip->do_not_publish[activity] = 0;
2178 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2179 status_id, (int)sip->do_not_publish[activity]);
2181 if (do_not_publish)
2183 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2184 return;
2187 g_free(sip->status);
2188 sip->status = g_strdup(status_id);
2190 /* hack to escape apostrof before comparison */
2191 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
2193 /* this will preserve OOF flag as well */
2194 if (!sipe_strequal(tmp, sip->note)) {
2195 sip->is_oof_note = FALSE;
2196 g_free(sip->note);
2197 sip->note = g_strdup(note);
2198 sip->note_since = time(NULL);
2200 g_free(tmp);
2202 /* schedule 2 sec to capture idle flag */
2203 action_name = g_strdup_printf("<%s>", "+set-status");
2204 sipe_schedule_action(action_name, SIPE_IDLE_SET_DELAY, (Action)send_presence_status, NULL, sip, NULL);
2205 g_free(action_name);
2209 static void
2210 sipe_set_idle(PurpleConnection * gc,
2211 int interval)
2213 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
2215 if (gc) {
2216 struct sipe_account_data *sip = gc->proto_data;
2218 if (sip) {
2219 sip->idle_switch = time(NULL);
2220 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
2225 static void
2226 sipe_alias_buddy(PurpleConnection *gc, const char *name,
2227 SIPE_UNUSED_PARAMETER const char *alias)
2229 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2230 sipe_group_set_user(sip, name);
2233 static void
2234 sipe_group_buddy(PurpleConnection *gc,
2235 const char *who,
2236 const char *old_group_name,
2237 const char *new_group_name)
2239 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2240 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
2241 struct sipe_group * old_group = NULL;
2242 struct sipe_group * new_group;
2244 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2245 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
2247 if(!buddy) { // buddy not in roaming list
2248 return;
2251 if (old_group_name) {
2252 old_group = sipe_group_find_by_name(sip, old_group_name);
2254 new_group = sipe_group_find_by_name(sip, new_group_name);
2256 if (old_group) {
2257 buddy->groups = g_slist_remove(buddy->groups, old_group);
2258 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
2261 if (!new_group) {
2262 sipe_group_create(sip, new_group_name, who);
2263 } else {
2264 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
2265 sipe_group_set_user(sip, who);
2269 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2271 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2273 /* libpurple can call us with undefined buddy or group */
2274 if (buddy && group) {
2275 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2277 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2278 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
2279 purple_blist_rename_buddy(buddy, buddy_name);
2280 g_free(buddy_name);
2282 /* Prepend sip: if needed */
2283 if (!g_str_has_prefix(buddy->name, "sip:")) {
2284 gchar *buf = sip_uri_from_name(buddy->name);
2285 purple_blist_rename_buddy(buddy, buf);
2286 g_free(buf);
2289 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
2290 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
2291 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
2292 b->name = g_strdup(buddy->name);
2293 b->just_added = TRUE;
2294 g_hash_table_insert(sip->buddies, b->name, b);
2295 sipe_group_buddy(gc, b->name, NULL, group->name);
2296 /* @TODO should go to callback */
2297 sipe_subscribe_presence_single(sip, b->name);
2298 } else {
2299 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
2304 static void sipe_free_buddy(struct sipe_buddy *buddy)
2306 #ifndef _WIN32
2308 * We are calling g_hash_table_foreach_steal(). That means that no
2309 * key/value deallocation functions are called. Therefore the glib
2310 * hash code does not touch the key (buddy->name) or value (buddy)
2311 * of the to-be-deleted hash node at all. It follows that we
2313 * - MUST free the memory for the key ourselves and
2314 * - ARE allowed to do it in this function
2316 * Conclusion: glib must be broken on the Windows platform if sipe
2317 * crashes with SIGTRAP when closing. You'll have to live
2318 * with the memory leak until this is fixed.
2320 g_free(buddy->name);
2321 #endif
2322 g_free(buddy->activity);
2323 g_free(buddy->meeting_subject);
2324 g_free(buddy->meeting_location);
2325 g_free(buddy->note);
2327 g_free(buddy->cal_start_time);
2328 g_free(buddy->cal_free_busy_base64);
2329 g_free(buddy->cal_free_busy);
2330 g_free(buddy->last_non_cal_activity);
2332 sipe_cal_free_working_hours(buddy->cal_working_hours);
2334 g_free(buddy->device_name);
2335 g_slist_free(buddy->groups);
2336 g_free(buddy);
2340 * Unassociates buddy from group first.
2341 * Then see if no groups left, removes buddy completely.
2342 * Otherwise updates buddy groups on server.
2344 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2346 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2347 struct sipe_buddy *b;
2348 struct sipe_group *g = NULL;
2350 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2351 if (!buddy) return;
2353 b = g_hash_table_lookup(sip->buddies, buddy->name);
2354 if (!b) return;
2356 if (group) {
2357 g = sipe_group_find_by_name(sip, group->name);
2360 if (g) {
2361 b->groups = g_slist_remove(b->groups, g);
2362 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2365 if (g_slist_length(b->groups) < 1) {
2366 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2367 sipe_cancel_scheduled_action(sip, action_name);
2368 g_free(action_name);
2370 g_hash_table_remove(sip->buddies, buddy->name);
2372 if (b->name) {
2373 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2374 send_soap_request(sip, body);
2375 g_free(body);
2378 sipe_free_buddy(b);
2379 } else {
2380 //updates groups on server
2381 sipe_group_set_user(sip, b->name);
2386 static void
2387 sipe_rename_group(PurpleConnection *gc,
2388 const char *old_name,
2389 PurpleGroup *group,
2390 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2392 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2393 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2394 if (s_group) {
2395 sipe_group_rename(sip, s_group, group->name);
2396 } else {
2397 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2401 static void
2402 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2404 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2405 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2406 if (s_group) {
2407 gchar *body;
2408 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2409 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2410 send_soap_request(sip, body);
2411 g_free(body);
2413 sip->groups = g_slist_remove(sip->groups, s_group);
2414 g_free(s_group->name);
2415 g_free(s_group);
2416 } else {
2417 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2421 /** All statuses need message attribute to pass Note */
2422 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
2424 PurpleStatusType *type;
2425 GList *types = NULL;
2427 /* Macros to reduce code repetition.
2428 Translators: noun */
2429 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2430 prim, id, name, \
2431 TRUE, user, FALSE, \
2432 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2433 NULL); \
2434 types = g_list_append(types, type);
2436 /* Online */
2437 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
2438 NULL,
2439 NULL,
2440 TRUE);
2442 /* Busy */
2443 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2444 sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id,
2445 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY),
2446 TRUE);
2448 /* Do Not Disturb */
2449 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2450 sipe_activity_map[SIPE_ACTIVITY_DND].status_id,
2451 NULL,
2452 TRUE);
2454 /* Away */
2455 /* Goes first in the list as
2456 * purple picks the first status with the AWAY type
2457 * for idle.
2459 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2460 NULL,
2461 NULL,
2462 TRUE);
2464 /* Be Right Back */
2465 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2466 sipe_activity_map[SIPE_ACTIVITY_BRB].status_id,
2467 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB),
2468 TRUE);
2470 /* Appear Offline */
2471 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE,
2472 NULL,
2473 NULL,
2474 TRUE);
2476 /* Offline */
2477 type = purple_status_type_new(PURPLE_STATUS_OFFLINE,
2478 NULL,
2479 NULL,
2480 TRUE);
2481 types = g_list_append(types, type);
2483 return types;
2487 * A callback for g_hash_table_foreach
2489 static void
2490 sipe_buddy_subscribe_cb(char *buddy_name,
2491 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2492 struct sipe_account_data *sip)
2494 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2495 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2496 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2497 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2499 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy_name));
2500 g_free(action_name);
2504 * Removes entries from purple buddy list
2505 * that does not correspond ones in the roaming contact list.
2507 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2508 GSList *buddies = purple_find_buddies(sip->account, NULL);
2509 GSList *entry = buddies;
2510 struct sipe_buddy *buddy;
2511 PurpleBuddy *b;
2512 PurpleGroup *g;
2514 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2515 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sip->buddies));
2516 while (entry) {
2517 b = entry->data;
2518 g = purple_buddy_get_group(b);
2519 buddy = g_hash_table_lookup(sip->buddies, b->name);
2520 if(buddy) {
2521 gboolean in_sipe_groups = FALSE;
2522 GSList *entry2 = buddy->groups;
2523 while (entry2) {
2524 struct sipe_group *group = entry2->data;
2525 if (sipe_strequal(group->name, g->name)) {
2526 in_sipe_groups = TRUE;
2527 break;
2529 entry2 = entry2->next;
2531 if(!in_sipe_groups) {
2532 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2533 purple_blist_remove_buddy(b);
2535 } else {
2536 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2537 purple_blist_remove_buddy(b);
2539 entry = entry->next;
2541 g_slist_free(buddies);
2544 static int
2545 sipe_find_access_level(struct sipe_account_data *sip,
2546 const gchar *type,
2547 const gchar *value,
2548 gboolean *is_group_access);
2550 static void
2551 sipe_refresh_blocked_status_cb(char *buddy_name,
2552 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2553 struct sipe_account_data *sip)
2555 int container_id = sipe_find_access_level(sip, "user", buddy_name, NULL);
2556 gboolean blocked = (container_id == 32000);
2557 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
2559 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
2560 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
2562 if (blocked != blocked_in_blist) {
2563 if (blocked) {
2564 purple_privacy_permit_remove(sip->account, buddy_name, TRUE);
2565 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
2566 } else {
2567 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
2568 purple_privacy_permit_add(sip->account, buddy_name, TRUE);
2571 /* stupid workaround to make pidgin re-render screen to reflect our changes */
2573 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
2574 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
2575 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
2577 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
2578 sipe_got_user_status(sip, buddy_name, purple_status_get_id(pstatus));
2584 static void
2585 sipe_refresh_blocked_status(struct sipe_account_data *sip)
2587 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_refresh_blocked_status_cb , (gpointer)sip);
2590 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2592 int len = msg->bodylen;
2594 const gchar *tmp = sipmsg_find_header(msg, "Event");
2595 const sipe_xml *item;
2596 sipe_xml *isc;
2597 const gchar *contacts_delta;
2598 const sipe_xml *group_node;
2599 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2600 return FALSE;
2603 /* Convert the contact from XML to Purple Buddies */
2604 isc = sipe_xml_parse(msg->body, len);
2605 if (!isc) {
2606 return FALSE;
2609 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
2610 if (contacts_delta) {
2611 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2614 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
2616 /* Parse groups */
2617 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
2618 struct sipe_group * group = g_new0(struct sipe_group, 1);
2619 const char *name = sipe_xml_attribute(group_node, "name");
2621 if (g_str_has_prefix(name, "~")) {
2622 name = _("Other Contacts");
2624 group->name = g_strdup(name);
2625 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
2627 sipe_group_add(sip, group);
2630 // Make sure we have at least one group
2631 if (g_slist_length(sip->groups) == 0) {
2632 struct sipe_group * group = g_new0(struct sipe_group, 1);
2633 PurpleGroup *purple_group;
2634 group->name = g_strdup(_("Other Contacts"));
2635 group->id = 1;
2636 purple_group = purple_group_new(group->name);
2637 purple_blist_add_group(purple_group, NULL);
2638 sip->groups = g_slist_append(sip->groups, group);
2641 /* Parse contacts */
2642 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
2643 const gchar *uri = sipe_xml_attribute(item, "uri");
2644 const gchar *name = sipe_xml_attribute(item, "name");
2645 gchar *buddy_name;
2646 struct sipe_buddy *buddy = NULL;
2647 gchar *tmp;
2648 gchar **item_groups;
2649 int i = 0;
2651 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2652 tmp = sip_uri_from_name(uri);
2653 buddy_name = g_ascii_strdown(tmp, -1);
2654 g_free(tmp);
2656 /* assign to group Other Contacts if nothing else received */
2657 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
2658 if(is_empty(tmp)) {
2659 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2660 g_free(tmp);
2661 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2663 item_groups = g_strsplit(tmp, " ", 0);
2664 g_free(tmp);
2666 while (item_groups[i]) {
2667 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2669 // If couldn't find the right group for this contact, just put them in the first group we have
2670 if (group == NULL && g_slist_length(sip->groups) > 0) {
2671 group = sip->groups->data;
2674 if (group != NULL) {
2675 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2676 if (!b){
2677 b = purple_buddy_new(sip->account, buddy_name, uri);
2678 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2680 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2683 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2684 if (name != NULL && strlen(name) != 0) {
2685 purple_blist_alias_buddy(b, name);
2687 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2691 if (!buddy) {
2692 buddy = g_new0(struct sipe_buddy, 1);
2693 buddy->name = g_strdup(b->name);
2694 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2697 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2699 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2700 } else {
2701 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2702 name);
2705 i++;
2706 } // while, contact groups
2707 g_strfreev(item_groups);
2708 g_free(buddy_name);
2710 } // for, contacts
2712 sipe_cleanup_local_blist(sip);
2714 /* Add self-contact if not there yet. 2005 systems. */
2715 /* This will resemble subscription to roaming_self in 2007 systems */
2716 if (!sip->ocs2007) {
2717 gchar *self_uri = sip_uri_self(sip);
2718 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, self_uri);
2720 if (!buddy) {
2721 buddy = g_new0(struct sipe_buddy, 1);
2722 buddy->name = g_strdup(self_uri);
2723 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2725 g_free(self_uri);
2728 sipe_xml_free(isc);
2730 /* subscribe to buddies */
2731 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2732 if (sip->batched_support) {
2733 sipe_subscribe_presence_batched(sip, NULL);
2734 } else {
2735 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2737 sip->subscribed_buddies = TRUE;
2739 /* for 2005 systems schedule contacts' status update
2740 * based on their calendar information
2742 if (!sip->ocs2007) {
2743 sipe_sched_calendar_status_update(sip, time(NULL));
2746 return 0;
2750 * Subscribe roaming contacts
2752 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2754 gchar *to = sip_uri_self(sip);
2755 gchar *tmp = get_contact(sip);
2756 gchar *hdr = g_strdup_printf(
2757 "Event: vnd-microsoft-roaming-contacts\r\n"
2758 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2759 "Supported: com.microsoft.autoextend\r\n"
2760 "Supported: ms-benotify\r\n"
2761 "Proxy-Require: ms-benotify\r\n"
2762 "Supported: ms-piggyback-first-notify\r\n"
2763 "Contact: %s\r\n", tmp);
2764 g_free(tmp);
2766 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2767 g_free(to);
2768 g_free(hdr);
2771 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2772 SIPE_UNUSED_PARAMETER void *unused)
2774 gchar *key;
2775 struct sip_dialog *dialog;
2776 gchar *to = sip_uri_self(sip);
2777 gchar *tmp = get_contact(sip);
2778 gchar *hdr = g_strdup_printf(
2779 "Event: presence.wpending\r\n"
2780 "Accept: text/xml+msrtc.wpending\r\n"
2781 "Supported: com.microsoft.autoextend\r\n"
2782 "Supported: ms-benotify\r\n"
2783 "Proxy-Require: ms-benotify\r\n"
2784 "Supported: ms-piggyback-first-notify\r\n"
2785 "Contact: %s\r\n", tmp);
2786 g_free(tmp);
2788 /* Subscription is identified by <event> key */
2789 key = g_strdup_printf("<%s>", "presence.wpending");
2790 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2791 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2793 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2795 g_free(to);
2796 g_free(hdr);
2797 g_free(key);
2801 * Fires on deregistration event initiated by server.
2802 * [MS-SIPREGE] SIP extension.
2805 // 2007 Example
2807 // Content-Type: text/registration-event
2808 // subscription-state: terminated;expires=0
2809 // ms-diagnostics-public: 4141;reason="User disabled"
2811 // deregistered;event=rejected
2813 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2815 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2816 gchar *event = NULL;
2817 gchar *reason = NULL;
2818 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2819 gchar *warning;
2821 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2822 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2824 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2825 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2826 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2827 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2828 } else {
2829 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2830 return;
2833 if (diagnostics != NULL) {
2834 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2835 } else { // for LCS2005
2836 int error_id = 0;
2837 if (event && sipe_strcase_equal(event, "unregistered")) {
2838 error_id = 4140; // [MS-SIPREGE]
2839 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2840 reason = g_strdup(_("you are already signed in at another location"));
2841 } else if (event && sipe_strcase_equal(event, "rejected")) {
2842 error_id = 4141;
2843 reason = g_strdup(_("user disabled")); // [MS-OCER]
2844 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2845 error_id = 4142;
2846 reason = g_strdup(_("user moved")); // [MS-OCER]
2849 g_free(event);
2850 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2851 g_free(reason);
2853 sip->gc->wants_to_die = TRUE;
2854 purple_connection_error(sip->gc, warning);
2855 g_free(warning);
2859 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2861 sipe_xml *xn_provision_group_list;
2862 const sipe_xml *node;
2864 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
2866 /* provisionGroup */
2867 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
2868 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
2869 g_free(sip->focus_factory_uri);
2870 sip->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
2871 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2872 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2873 break;
2876 sipe_xml_free(xn_provision_group_list);
2879 /** for 2005 system */
2880 static void
2881 sipe_process_provisioning(struct sipe_account_data *sip,
2882 struct sipmsg *msg)
2884 sipe_xml *xn_provision;
2885 const sipe_xml *node;
2887 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
2888 if ((node = sipe_xml_child(xn_provision, "user"))) {
2889 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
2890 if ((node = sipe_xml_child(node, "line"))) {
2891 const gchar *line_uri = sipe_xml_attribute(node, "uri");
2892 const gchar *server = sipe_xml_attribute(node, "server");
2893 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2894 sip_csta_open(sip, line_uri, server);
2897 sipe_xml_free(xn_provision);
2900 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2902 const gchar *contacts_delta;
2903 sipe_xml *xml;
2905 xml = sipe_xml_parse(msg->body, msg->bodylen);
2906 if (!xml)
2908 return;
2911 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
2912 if (contacts_delta)
2914 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2917 sipe_xml_free(xml);
2920 static void
2921 free_container_member(struct sipe_container_member *member)
2923 if (!member) return;
2925 g_free(member->type);
2926 g_free(member->value);
2927 g_free(member);
2930 static void
2931 free_container(struct sipe_container *container)
2933 GSList *entry;
2935 if (!container) return;
2937 entry = container->members;
2938 while (entry) {
2939 void *data = entry->data;
2940 entry = g_slist_remove(entry, data);
2941 free_container_member((struct sipe_container_member *)data);
2943 g_free(container);
2946 static void
2947 sipe_send_container_members_prepare(const guint container_id,
2948 const guint container_version,
2949 const gchar *action,
2950 const gchar *type,
2951 const gchar *value,
2952 char **container_xmls)
2954 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2955 gchar *body;
2957 if (!container_xmls) return;
2959 body = g_strdup_printf(
2960 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2961 container_id,
2962 container_version,
2963 action,
2964 type,
2965 value_str);
2966 g_free(value_str);
2968 if ((*container_xmls) == NULL) {
2969 *container_xmls = body;
2970 } else {
2971 char *tmp = *container_xmls;
2973 *container_xmls = g_strconcat(*container_xmls, body, NULL);
2974 g_free(tmp);
2975 g_free(body);
2979 static void
2980 sipe_send_set_container_members(struct sipe_account_data *sip,
2981 char *container_xmls)
2983 gchar *self;
2984 gchar *contact;
2985 gchar *hdr;
2986 gchar *body;
2988 if (!container_xmls) return;
2990 self = sip_uri_self(sip);
2991 body = g_strdup_printf(
2992 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2993 "%s"
2994 "</setContainerMembers>",
2995 container_xmls);
2997 contact = get_contact(sip);
2998 hdr = g_strdup_printf("Contact: %s\r\n"
2999 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
3000 g_free(contact);
3002 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
3004 g_free(hdr);
3005 g_free(body);
3006 g_free(self);
3010 * Finds locally stored MS-PRES container member
3012 static struct sipe_container_member *
3013 sipe_find_container_member(struct sipe_container *container,
3014 const gchar *type,
3015 const gchar *value)
3017 struct sipe_container_member *member;
3018 GSList *entry;
3020 if (container == NULL || type == NULL) {
3021 return NULL;
3024 entry = container->members;
3025 while (entry) {
3026 member = entry->data;
3027 if (sipe_strcase_equal(member->type, type) &&
3028 sipe_strcase_equal(member->value, value))
3030 return member;
3032 entry = entry->next;
3034 return NULL;
3038 * Finds locally stored MS-PRES container by id
3040 static struct sipe_container *
3041 sipe_find_container(struct sipe_account_data *sip,
3042 guint id)
3044 struct sipe_container *container;
3045 GSList *entry;
3047 if (sip == NULL) {
3048 return NULL;
3051 entry = sip->containers;
3052 while (entry) {
3053 container = entry->data;
3054 if (id == container->id) {
3055 return container;
3057 entry = entry->next;
3059 return NULL;
3062 static GSList *
3063 sipe_get_access_domains(struct sipe_account_data *sip)
3065 struct sipe_container *container;
3066 struct sipe_container_member *member;
3067 GSList *entry;
3068 GSList *entry2;
3069 GSList *res = NULL;
3071 if (!sip) return NULL;
3073 entry = sip->containers;
3074 while (entry) {
3075 container = entry->data;
3077 entry2 = container->members;
3078 while (entry2) {
3079 member = entry2->data;
3080 if (sipe_strcase_equal(member->type, "domain"))
3082 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
3084 entry2 = entry2->next;
3086 entry = entry->next;
3088 return res;
3092 * Returns pointer to domain part in provided Email URL
3094 * @param email an email URL. Example: first.last@hq.company.com
3095 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
3097 * Doesn't allocate memory
3099 static const char *
3100 sipe_get_domain(const char *email)
3102 char *tmp;
3104 if (!email) return NULL;
3106 tmp = strstr(email, "@");
3108 if (tmp && ((tmp+1) < (email + strlen(email)))) {
3109 return tmp+1;
3110 } else {
3111 return NULL;
3116 /* @TODO: replace with binary search for faster access? */
3117 /** source: http://support.microsoft.com/kb/897567 */
3118 static const char * const public_domains [] = {
3119 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
3120 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
3121 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
3122 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
3123 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
3124 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
3125 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
3126 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
3127 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
3128 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
3129 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
3130 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
3131 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
3132 "yahoo.com",
3133 NULL};
3135 static gboolean
3136 sipe_is_public_domain(const char *domain)
3138 int i = 0;
3139 while (public_domains[i]) {
3140 if (sipe_strcase_equal(public_domains[i], domain)) {
3141 return TRUE;
3143 i++;
3145 return FALSE;
3149 * Access Levels
3150 * 32000 - Blocked
3151 * 400 - Personal
3152 * 300 - Team
3153 * 200 - Company
3154 * 100 - Public
3156 static const char *
3157 sipe_get_access_level_name(int container_id)
3159 switch(container_id) {
3160 case 32000: return _("Blocked");
3161 case 400: return _("Personal");
3162 case 300: return _("Team");
3163 case 200: return _("Company");
3164 case 100: return _("Public");
3166 return _("Unknown");
3169 static const guint containers[] = {32000, 400, 300, 200, 100};
3170 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3173 static int
3174 sipe_find_member_access_level(struct sipe_account_data *sip,
3175 const gchar *type,
3176 const gchar *value)
3178 unsigned int i = 0;
3179 const gchar *value_mod = value;
3181 if (!type) return -1;
3183 if (sipe_strequal("user", type)) {
3184 value_mod = sipe_get_no_sip_uri(value);
3187 for (i = 0; i < CONTAINERS_LEN; i++) {
3188 struct sipe_container_member *member;
3189 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3190 if (!container) continue;
3192 member = sipe_find_container_member(container, type, value_mod);
3193 if (member) return containers[i];
3196 return -1;
3199 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3200 static int
3201 sipe_find_access_level(struct sipe_account_data *sip,
3202 const gchar *type,
3203 const gchar *value,
3204 gboolean *is_group_access)
3206 int container_id = -1;
3208 if (sipe_strequal("user", type)) {
3209 const char *domain;
3210 const char *no_sip_uri = sipe_get_no_sip_uri(value);
3212 container_id = sipe_find_member_access_level(sip, "user", no_sip_uri);
3213 if (container_id >= 0) {
3214 if (is_group_access) *is_group_access = FALSE;
3215 return container_id;
3218 domain = sipe_get_domain(no_sip_uri);
3219 container_id = sipe_find_member_access_level(sip, "domain", domain);
3220 if (container_id >= 0) {
3221 if (is_group_access) *is_group_access = TRUE;
3222 return container_id;
3225 container_id = sipe_find_member_access_level(sip, "sameEnterprise", NULL);
3226 if ((container_id >= 0) && sipe_strcase_equal(sip->sipdomain, domain)) {
3227 if (is_group_access) *is_group_access = TRUE;
3228 return container_id;
3231 container_id = sipe_find_member_access_level(sip, "publicCloud", NULL);
3232 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
3233 if (is_group_access) *is_group_access = TRUE;
3234 return container_id;
3237 container_id = sipe_find_member_access_level(sip, "everyone", NULL);
3238 if ((container_id >= 0)) {
3239 if (is_group_access) *is_group_access = TRUE;
3240 return container_id;
3242 } else {
3243 container_id = sipe_find_member_access_level(sip, type, value);
3244 if (is_group_access) *is_group_access = FALSE;
3247 return container_id;
3251 * @param container_id a new access level. If -1 then current access level
3252 * is just removed (I.e. the member is removed from all containers).
3253 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
3254 * @param value a value for member. E.g. SIP URI for "user" member type.
3256 static void
3257 sipe_change_access_level(struct sipe_account_data *sip,
3258 const int container_id,
3259 const gchar *type,
3260 const gchar *value)
3262 unsigned int i;
3263 int current_container_id = -1;
3264 char *container_xmls = NULL;
3266 /* for each container: find/delete */
3267 for (i = 0; i < CONTAINERS_LEN; i++) {
3268 struct sipe_container_member *member;
3269 struct sipe_container *container = sipe_find_container(sip, containers[i]);
3271 if (!container) continue;
3273 member = sipe_find_container_member(container, type, value);
3274 if (member) {
3275 current_container_id = containers[i];
3276 /* delete/publish current access level */
3277 if (container_id < 0 || container_id != current_container_id) {
3278 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
3279 /* remove member from our cache, to be able to recalculate AL below */
3280 container->members = g_slist_remove(container->members, member);
3281 current_container_id = -1;
3286 /* recalculate AL below */
3287 current_container_id = sipe_find_access_level(sip, type, value, NULL);
3289 /* assign/publish new access level */
3290 if (container_id != current_container_id && container_id >= 0) {
3291 struct sipe_container *container = sipe_find_container(sip, container_id);
3292 guint version = container ? container->version : 0;
3294 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
3297 if (container_xmls) {
3298 sipe_send_set_container_members(sip, container_xmls);
3300 g_free(container_xmls);
3303 static void
3304 free_publication(struct sipe_publication *publication)
3306 g_free(publication->category);
3307 g_free(publication->cal_event_hash);
3308 g_free(publication->note);
3310 g_free(publication->working_hours_xml_str);
3311 g_free(publication->fb_start_str);
3312 g_free(publication->free_busy_base64);
3314 g_free(publication);
3317 /* key is <category><instance><container> */
3318 static gboolean
3319 sipe_is_our_publication(struct sipe_account_data *sip,
3320 const gchar *key)
3322 GSList *entry;
3324 /* filling keys for our publications if not yet cached */
3325 if (!sip->our_publication_keys) {
3326 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
3327 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
3328 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
3329 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
3330 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
3331 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
3332 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
3334 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3335 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
3336 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
3337 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
3338 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
3339 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
3340 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
3341 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
3342 SIPE_DEBUG_INFO("\tNote : %u", 0);
3343 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3345 /* device */
3346 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3347 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
3349 /* state:machineState */
3350 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3351 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
3352 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3353 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
3355 /* state:userState */
3356 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3357 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
3358 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3359 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
3361 /* state:calendarState */
3362 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3363 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
3364 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3365 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
3367 /* state:calendarState OOF */
3368 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3369 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
3370 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3371 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
3373 /* note */
3374 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3375 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3376 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3377 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3378 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3379 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3381 /* note OOF */
3382 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3383 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
3384 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3385 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
3386 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3387 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
3389 /* calendarData:WorkingHours */
3390 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3391 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3392 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3393 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3394 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3395 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3396 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3397 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3398 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3399 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3400 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3401 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3403 /* calendarData:FreeBusy */
3404 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3405 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3406 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3407 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3408 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3409 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3410 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3411 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3412 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3413 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3414 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3415 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3417 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3418 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3421 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3423 entry = sip->our_publication_keys;
3424 while (entry) {
3425 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3426 if (sipe_strequal(entry->data, key)) {
3427 return TRUE;
3429 entry = entry->next;
3431 return FALSE;
3434 /** Property names to store in blist.xml */
3435 #define ALIAS_PROP "alias"
3436 #define EMAIL_PROP "email"
3437 #define PHONE_PROP "phone"
3438 #define PHONE_DISPLAY_PROP "phone-display"
3439 #define PHONE_MOBILE_PROP "phone-mobile"
3440 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3441 #define PHONE_HOME_PROP "phone-home"
3442 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3443 #define PHONE_OTHER_PROP "phone-other"
3444 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3445 #define PHONE_CUSTOM1_PROP "phone-custom1"
3446 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3447 #define SITE_PROP "site"
3448 #define COMPANY_PROP "company"
3449 #define DEPARTMENT_PROP "department"
3450 #define TITLE_PROP "title"
3451 #define OFFICE_PROP "office"
3452 /** implies work address */
3453 #define ADDRESS_STREET_PROP "address-street"
3454 #define ADDRESS_CITY_PROP "address-city"
3455 #define ADDRESS_STATE_PROP "address-state"
3456 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3457 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3460 * Tries to figure out user first and last name
3461 * based on Display Name and email properties.
3463 * Allocates memory - must be g_free()'d
3465 * Examples to parse:
3466 * First Last
3467 * First Last - Company Name
3468 * Last, First
3469 * Last, First M.
3470 * Last, First (C)(STP) (Company)
3471 * first.last@company.com (preprocessed as "first last")
3472 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3474 * Unusable examples:
3475 * user@company.com (preprocessed as "user")
3476 * first.m.last@company.com (preprocessed as "first m last")
3477 * user.company.com@reuters.net (preprocessed as "user company com")
3479 static void
3480 sipe_get_first_last_names(struct sipe_account_data *sip,
3481 const char *uri,
3482 char **first_name,
3483 char **last_name)
3485 PurpleBuddy *p_buddy;
3486 char *display_name;
3487 const char *email;
3488 const char *first, *last;
3489 char *tmp;
3490 char **parts;
3491 gboolean has_comma = FALSE;
3493 if (!sip || !uri) return;
3495 p_buddy = purple_find_buddy(sip->account, uri);
3497 if (!p_buddy) return;
3499 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3500 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3502 if (!display_name && !email) return;
3504 /* if no display name, make "first last anything_else" out of email */
3505 if (email && !display_name) {
3506 display_name = g_strndup(email, strstr(email, "@") - email);
3507 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3508 g_free(tmp);
3511 if (display_name) {
3512 has_comma = (strstr(display_name, ",") != NULL);
3513 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3514 g_free(tmp);
3515 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3516 g_free(tmp);
3519 parts = g_strsplit(display_name, " ", 0);
3521 if (!parts[0] || !parts[1]) {
3522 g_free(display_name);
3523 g_strfreev(parts);
3524 return;
3527 if (has_comma) {
3528 last = parts[0];
3529 first = parts[1];
3530 } else {
3531 first = parts[0];
3532 last = parts[1];
3535 if (first_name) {
3536 *first_name = g_strstrip(g_strdup(first));
3539 if (last_name) {
3540 *last_name = g_strstrip(g_strdup(last));
3543 g_free(display_name);
3544 g_strfreev(parts);
3548 * Update user information
3550 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3551 * @param property_name
3552 * @param property_value may be modified to strip white space
3554 static void
3555 sipe_update_user_info(struct sipe_account_data *sip,
3556 const char *uri,
3557 const char *property_name,
3558 char *property_value)
3560 GSList *buddies, *entry;
3562 if (!property_name || strlen(property_name) == 0) return;
3564 if (property_value)
3565 property_value = g_strstrip(property_value);
3567 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3568 while (entry) {
3569 const char *prop_str;
3570 const char *server_alias;
3571 PurpleBuddy *p_buddy = entry->data;
3573 /* for Display Name */
3574 if (sipe_strequal(property_name, ALIAS_PROP)) {
3575 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3576 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3577 purple_blist_alias_buddy(p_buddy, property_value);
3580 server_alias = purple_buddy_get_server_alias(p_buddy);
3581 if (!is_empty(property_value) &&
3582 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3584 purple_blist_server_alias_buddy(p_buddy, property_value);
3587 /* for other properties */
3588 else {
3589 if (!is_empty(property_value)) {
3590 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3591 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3592 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3597 entry = entry->next;
3599 g_slist_free(buddies);
3603 * Update user phone
3604 * Suitable for both 2005 and 2007 systems.
3606 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3607 * @param phone_type
3608 * @param phone may be modified to strip white space
3609 * @param phone_display_string may be modified to strip white space
3611 static void
3612 sipe_update_user_phone(struct sipe_account_data *sip,
3613 const char *uri,
3614 const gchar *phone_type,
3615 gchar *phone,
3616 gchar *phone_display_string)
3618 const char *phone_node = PHONE_PROP; /* work phone by default */
3619 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3621 if(!phone || strlen(phone) == 0) return;
3623 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3624 phone_node = PHONE_MOBILE_PROP;
3625 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3626 } else if (sipe_strequal(phone_type, "home")) {
3627 phone_node = PHONE_HOME_PROP;
3628 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3629 } else if (sipe_strequal(phone_type, "other")) {
3630 phone_node = PHONE_OTHER_PROP;
3631 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3632 } else if (sipe_strequal(phone_type, "custom1")) {
3633 phone_node = PHONE_CUSTOM1_PROP;
3634 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3637 sipe_update_user_info(sip, uri, phone_node, phone);
3638 if (phone_display_string) {
3639 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3643 static void
3644 sipe_update_calendar(struct sipe_account_data *sip)
3646 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3648 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3650 if (sipe_strequal(calendar, "EXCH")) {
3651 sipe_ews_update_calendar(sip);
3654 /* schedule repeat */
3655 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL, (Action)sipe_update_calendar, NULL, sip, NULL);
3657 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3661 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3662 * by using standard Purple's means of signals and saved statuses.
3664 * Thus all UI elements get updated: Status Button with Note, docklet.
3665 * This is ablolutely important as both our status and note can come
3666 * inbound (roaming) or be updated programmatically (e.g. based on our
3667 * calendar data).
3669 static void
3670 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3671 const char *status_id,
3672 const char *message,
3673 time_t do_not_publish[])
3675 PurpleStatus *status = purple_account_get_active_status(account);
3676 gboolean changed = TRUE;
3678 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3679 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3681 changed = FALSE;
3684 if (purple_savedstatus_is_idleaway()) {
3685 changed = FALSE;
3688 if (changed) {
3689 PurpleSavedStatus *saved_status;
3690 const PurpleStatusType *acct_status_type =
3691 purple_status_type_find_with_id(account->status_types, status_id);
3692 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3693 sipe_activity activity = sipe_get_activity_by_token(status_id);
3695 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3696 if (saved_status) {
3697 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3700 /* If this type+message is unique then create a new transient saved status
3701 * Ref: gtkstatusbox.c
3703 if (!saved_status) {
3704 GList *tmp;
3705 GList *active_accts = purple_accounts_get_all_active();
3707 saved_status = purple_savedstatus_new(NULL, primitive);
3708 purple_savedstatus_set_message(saved_status, message);
3710 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3711 purple_savedstatus_set_substatus(saved_status,
3712 (PurpleAccount *)tmp->data, acct_status_type, message);
3714 g_list_free(active_accts);
3717 do_not_publish[activity] = time(NULL);
3718 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3719 status_id, (int)do_not_publish[activity]);
3721 /* Set the status for each account */
3722 purple_savedstatus_activate(saved_status);
3726 struct hash_table_delete_payload {
3727 GHashTable *hash_table;
3728 guint container;
3731 static void
3732 sipe_remove_category_container_publications_cb(const char *name,
3733 struct sipe_publication *publication,
3734 struct hash_table_delete_payload *payload)
3736 if (publication->container == payload->container) {
3737 g_hash_table_remove(payload->hash_table, name);
3740 static void
3741 sipe_remove_category_container_publications(GHashTable *our_publications,
3742 const char *category,
3743 guint container)
3745 struct hash_table_delete_payload payload;
3746 payload.hash_table = g_hash_table_lookup(our_publications, category);
3748 if (!payload.hash_table) return;
3750 payload.container = container;
3751 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3754 static void
3755 send_publish_category_initial(struct sipe_account_data *sip);
3758 * When we receive some self (BE) NOTIFY with a new subscriber
3759 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3762 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3764 gchar *contact;
3765 gchar *to;
3766 sipe_xml *xml;
3767 const sipe_xml *node;
3768 const sipe_xml *node2;
3769 char *display_name = NULL;
3770 char *uri;
3771 GSList *category_names = NULL;
3772 int aggreg_avail = 0;
3773 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3774 gboolean do_update_status = FALSE;
3775 gboolean has_note_cleaned = FALSE;
3777 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3779 xml = sipe_xml_parse(msg->body, msg->bodylen);
3780 if (!xml) return;
3782 contact = get_contact(sip);
3783 to = sip_uri_self(sip);
3786 /* categories */
3787 /* set list of categories participating in this XML */
3788 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3789 const gchar *name = sipe_xml_attribute(node, "name");
3790 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3792 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3793 category_names ? (int) g_slist_length(category_names) : -1);
3794 /* drop category information */
3795 if (category_names) {
3796 GSList *entry = category_names;
3797 while (entry) {
3798 GHashTable *cat_publications;
3799 const gchar *category = entry->data;
3800 entry = entry->next;
3801 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3802 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3803 if (cat_publications) {
3804 g_hash_table_remove(sip->our_publications, category);
3805 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3809 g_slist_free(category_names);
3810 /* filling our categories reflected in roaming data */
3811 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3812 const char *tmp;
3813 const gchar *name = sipe_xml_attribute(node, "name");
3814 guint container = sipe_xml_int_attribute(node, "container", -1);
3815 guint instance = sipe_xml_int_attribute(node, "instance", -1);
3816 guint version = sipe_xml_int_attribute(node, "version", 0);
3817 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
3818 sipe_utils_str_to_time(tmp) : 0;
3819 gchar *key;
3820 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3822 /* Ex. clear note: <category name="note"/> */
3823 if (container == (guint)-1) {
3824 g_free(sip->note);
3825 sip->note = NULL;
3826 do_update_status = TRUE;
3827 continue;
3830 /* Ex. clear note: <category name="note" container="200"/> */
3831 if (instance == (guint)-1) {
3832 if (container == 200) {
3833 g_free(sip->note);
3834 sip->note = NULL;
3835 do_update_status = TRUE;
3837 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3838 sipe_remove_category_container_publications(
3839 sip->our_publications, name, container);
3840 continue;
3843 /* key is <category><instance><container> */
3844 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3845 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3847 /* capture all userState publication for later clean up if required */
3848 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3849 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3851 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
3852 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3853 publication->category = g_strdup(name);
3854 publication->instance = instance;
3855 publication->container = container;
3856 publication->version = version;
3858 if (!sip->user_state_publications) {
3859 sip->user_state_publications = g_hash_table_new_full(
3860 g_str_hash, g_str_equal,
3861 g_free, (GDestroyNotify)free_publication);
3863 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3864 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3865 key, version);
3869 if (sipe_is_our_publication(sip, key)) {
3870 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3872 publication->category = g_strdup(name);
3873 publication->instance = instance;
3874 publication->container = container;
3875 publication->version = version;
3877 /* filling publication->availability */
3878 if (sipe_strequal(name, "state")) {
3879 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3880 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3882 if (xn_avail) {
3883 gchar *avail_str = sipe_xml_data(xn_avail);
3884 if (avail_str) {
3885 publication->availability = atoi(avail_str);
3887 g_free(avail_str);
3889 /* for calendarState */
3890 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
3891 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3892 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3894 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
3895 if (xn_activity) {
3896 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
3897 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3899 event->is_meeting = TRUE;
3902 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
3903 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
3905 publication->cal_event_hash = sipe_cal_event_hash(event);
3906 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3907 publication->cal_event_hash);
3908 sipe_cal_event_free(event);
3911 /* filling publication->note */
3912 if (sipe_strequal(name, "note")) {
3913 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
3915 if (!has_note_cleaned) {
3916 has_note_cleaned = TRUE;
3918 g_free(sip->note);
3919 sip->note = NULL;
3920 sip->note_since = publish_time;
3922 do_update_status = TRUE;
3925 g_free(publication->note);
3926 publication->note = NULL;
3927 if (xn_body) {
3928 char *tmp;
3930 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
3931 g_free(tmp);
3932 if (publish_time >= sip->note_since) {
3933 g_free(sip->note);
3934 sip->note = g_strdup(publication->note);
3935 sip->note_since = publish_time;
3936 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
3938 do_update_status = TRUE;
3943 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3944 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3945 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
3946 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
3947 if (xn_free_busy) {
3948 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3949 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
3951 if (xn_working_hours) {
3952 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
3956 if (!cat_publications) {
3957 cat_publications = g_hash_table_new_full(
3958 g_str_hash, g_str_equal,
3959 g_free, (GDestroyNotify)free_publication);
3960 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3961 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3963 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3964 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3966 g_free(key);
3968 /* aggregateState (not an our publication) from 2-nd container */
3969 if (sipe_strequal(name, "state") && container == 2) {
3970 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3972 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
3973 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3974 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3976 if (xn_avail) {
3977 gchar *avail_str = sipe_xml_data(xn_avail);
3978 if (avail_str) {
3979 aggreg_avail = atoi(avail_str);
3981 g_free(avail_str);
3984 if (xn_activity) {
3985 const char *activity_token = sipe_xml_attribute(xn_activity, "token");
3987 aggreg_activity = sipe_get_activity_by_token(activity_token);
3990 do_update_status = TRUE;
3994 /* userProperties published by server from AD */
3995 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3996 const sipe_xml *line;
3997 /* line, for Remote Call Control (RCC) */
3998 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
3999 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
4000 const gchar *line_type = sipe_xml_attribute(line, "lineType");
4001 gchar *line_uri;
4003 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
4005 line_uri = sipe_xml_data(line);
4006 if (line_uri) {
4007 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
4008 sip_csta_open(sip, line_uri, line_server);
4010 g_free(line_uri);
4012 break;
4016 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
4017 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
4019 /* containers */
4020 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
4021 guint id = sipe_xml_int_attribute(node, "id", 0);
4022 struct sipe_container *container = sipe_find_container(sip, id);
4024 if (container) {
4025 sip->containers = g_slist_remove(sip->containers, container);
4026 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
4027 free_container(container);
4029 container = g_new0(struct sipe_container, 1);
4030 container->id = id;
4031 container->version = sipe_xml_int_attribute(node, "version", 0);
4032 sip->containers = g_slist_append(sip->containers, container);
4033 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
4035 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
4036 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
4037 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
4038 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
4039 container->members = g_slist_append(container->members, member);
4040 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
4041 member->type, member->value ? member->value : "");
4045 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
4046 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
4047 char *container_xmls = NULL;
4048 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL, NULL);
4049 int federatedAL = sipe_find_access_level(sip, "federated", NULL, NULL);
4051 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
4052 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
4053 /* initial set-up to let counterparties see your status */
4054 if (sameEnterpriseAL < 0) {
4055 struct sipe_container *container = sipe_find_container(sip, 200);
4056 guint version = container ? container->version : 0;
4057 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
4059 if (federatedAL < 0) {
4060 struct sipe_container *container = sipe_find_container(sip, 100);
4061 guint version = container ? container->version : 0;
4062 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
4064 sip->access_level_set = TRUE;
4066 if (container_xmls) {
4067 sipe_send_set_container_members(sip, container_xmls);
4069 g_free(container_xmls);
4072 /* Refresh contacts' blocked status */
4073 sipe_refresh_blocked_status(sip);
4075 /* subscribers */
4076 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
4077 const char *user;
4078 const char *acknowledged;
4079 gchar *hdr;
4080 gchar *body;
4082 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
4083 if (!user) continue;
4084 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
4085 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
4086 uri = sip_uri_from_name(user);
4088 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
4090 acknowledged= sipe_xml_attribute(node, "acknowledged");
4091 if(sipe_strcase_equal(acknowledged,"false")){
4092 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
4093 if (!purple_find_buddy(sip->account, uri)) {
4094 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
4097 hdr = g_strdup_printf(
4098 "Contact: %s\r\n"
4099 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
4101 body = g_strdup_printf(
4102 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
4103 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
4104 "</setSubscribers>", user);
4106 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
4107 g_free(body);
4108 g_free(hdr);
4110 g_free(display_name);
4111 g_free(uri);
4114 g_free(contact);
4115 sipe_xml_free(xml);
4117 /* Publish initial state if not yet.
4118 * Assuming this happens on initial responce to subscription to roaming-self
4119 * so we've already updated our roaming data in full.
4120 * Only for 2007+
4122 if (!sip->initial_state_published) {
4123 send_publish_category_initial(sip);
4124 sip->initial_state_published = TRUE;
4125 /* dalayed run */
4126 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
4127 do_update_status = FALSE;
4128 } else if (aggreg_avail) {
4130 g_free(sip->status);
4131 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
4132 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
4133 } else {
4134 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
4138 if (do_update_status) {
4139 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
4140 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
4143 g_free(to);
4146 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
4148 gchar *to = sip_uri_self(sip);
4149 gchar *tmp = get_contact(sip);
4150 gchar *hdr = g_strdup_printf(
4151 "Event: vnd-microsoft-roaming-ACL\r\n"
4152 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
4153 "Supported: com.microsoft.autoextend\r\n"
4154 "Supported: ms-benotify\r\n"
4155 "Proxy-Require: ms-benotify\r\n"
4156 "Supported: ms-piggyback-first-notify\r\n"
4157 "Contact: %s\r\n", tmp);
4158 g_free(tmp);
4160 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
4161 g_free(to);
4162 g_free(hdr);
4166 * To request for presence information about the user, access level settings that have already been configured by the user
4167 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
4168 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
4171 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
4173 gchar *to = sip_uri_self(sip);
4174 gchar *tmp = get_contact(sip);
4175 gchar *hdr = g_strdup_printf(
4176 "Event: vnd-microsoft-roaming-self\r\n"
4177 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
4178 "Supported: ms-benotify\r\n"
4179 "Proxy-Require: ms-benotify\r\n"
4180 "Supported: ms-piggyback-first-notify\r\n"
4181 "Contact: %s\r\n"
4182 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
4184 gchar *body=g_strdup(
4185 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
4186 "<roaming type=\"categories\"/>"
4187 "<roaming type=\"containers\"/>"
4188 "<roaming type=\"subscribers\"/></roamingList>");
4190 g_free(tmp);
4191 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4192 g_free(body);
4193 g_free(to);
4194 g_free(hdr);
4198 * For 2005 version
4200 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
4202 gchar *to = sip_uri_self(sip);
4203 gchar *tmp = get_contact(sip);
4204 gchar *hdr = g_strdup_printf(
4205 "Event: vnd-microsoft-provisioning\r\n"
4206 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
4207 "Supported: com.microsoft.autoextend\r\n"
4208 "Supported: ms-benotify\r\n"
4209 "Proxy-Require: ms-benotify\r\n"
4210 "Supported: ms-piggyback-first-notify\r\n"
4211 "Expires: 0\r\n"
4212 "Contact: %s\r\n", tmp);
4214 g_free(tmp);
4215 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
4216 g_free(to);
4217 g_free(hdr);
4220 /** Subscription for provisioning information to help with initial
4221 * configuration. This subscription is a one-time query (denoted by the Expires header,
4222 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4223 * configuration, meeting policies, and policy settings that Communicator must enforce.
4224 * TODO: for what we need this information.
4227 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
4229 gchar *to = sip_uri_self(sip);
4230 gchar *tmp = get_contact(sip);
4231 gchar *hdr = g_strdup_printf(
4232 "Event: vnd-microsoft-provisioning-v2\r\n"
4233 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4234 "Supported: com.microsoft.autoextend\r\n"
4235 "Supported: ms-benotify\r\n"
4236 "Proxy-Require: ms-benotify\r\n"
4237 "Supported: ms-piggyback-first-notify\r\n"
4238 "Expires: 0\r\n"
4239 "Contact: %s\r\n"
4240 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
4241 gchar *body = g_strdup(
4242 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4243 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4244 "<provisioningGroup name=\"ucPolicy\"/>"
4245 "</provisioningGroupList>");
4247 g_free(tmp);
4248 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
4249 g_free(body);
4250 g_free(to);
4251 g_free(hdr);
4254 static void
4255 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
4256 gpointer value, gpointer user_data)
4258 struct sip_subscription *subscription = value;
4259 struct sip_dialog *dialog = &subscription->dialog;
4260 struct sipe_account_data *sip = user_data;
4261 gchar *tmp = get_contact(sip);
4262 gchar *hdr = g_strdup_printf(
4263 "Event: %s\r\n"
4264 "Expires: 0\r\n"
4265 "Contact: %s\r\n", subscription->event, tmp);
4266 g_free(tmp);
4268 /* Rate limit to max. 25 requests per seconds */
4269 g_usleep(1000000 / 25);
4271 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
4272 g_free(hdr);
4275 /* IM Session (INVITE and MESSAGE methods) */
4277 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4278 static gchar *
4279 get_end_points (struct sipe_account_data *sip,
4280 struct sip_session *session)
4282 gchar *res;
4284 if (session == NULL) {
4285 return NULL;
4288 res = g_strdup_printf("<sip:%s>", sip->username);
4290 SIPE_DIALOG_FOREACH {
4291 gchar *tmp = res;
4292 res = g_strdup_printf("%s, <%s>", res, dialog->with);
4293 g_free(tmp);
4295 if (dialog->theirepid) {
4296 tmp = res;
4297 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
4298 g_free(tmp);
4300 } SIPE_DIALOG_FOREACH_END;
4302 return res;
4305 static gboolean
4306 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
4307 struct sipmsg *msg,
4308 SIPE_UNUSED_PARAMETER struct transaction *trans)
4310 gboolean ret = TRUE;
4312 if (msg->response != 200) {
4313 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
4314 return FALSE;
4317 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
4319 return ret;
4323 * Asks UA/proxy about its capabilities.
4325 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
4327 gchar *to = sip_uri(who);
4328 gchar *contact = get_contact(sip);
4329 gchar *request = g_strdup_printf(
4330 "Accept: application/sdp\r\n"
4331 "Contact: %s\r\n", contact);
4332 g_free(contact);
4334 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
4336 g_free(to);
4337 g_free(request);
4340 static void
4341 sipe_notify_user(struct sipe_account_data *sip,
4342 struct sip_session *session,
4343 PurpleMessageFlags flags,
4344 const gchar *message)
4346 PurpleConversation *conv;
4348 if (!session->conv) {
4349 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
4350 } else {
4351 conv = session->conv;
4353 purple_conversation_write(conv, NULL, message, flags, time(NULL));
4356 void
4357 sipe_present_info(struct sipe_account_data *sip,
4358 struct sip_session *session,
4359 const gchar *message)
4361 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
4364 static void
4365 sipe_present_err(struct sipe_account_data *sip,
4366 struct sip_session *session,
4367 const gchar *message)
4369 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
4372 void
4373 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
4374 struct sip_session *session,
4375 int sip_error,
4376 int sip_warning,
4377 const gchar *who,
4378 const gchar *message)
4380 char *msg, *msg_tmp, *msg_tmp2;
4381 const char *label;
4383 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
4384 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
4385 g_free(msg_tmp);
4386 /* Service unavailable; Server Internal Error; Server Time-out */
4387 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4388 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4389 g_free(msg);
4390 msg = NULL;
4391 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4392 label = _("This message was not delivered to %s because the service is not available");
4393 } else if (sip_error == 486) { /* Busy Here */
4394 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4395 } else if (sip_error == 415) { /* Unsupported media type */
4396 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4397 } else {
4398 label = _("This message was not delivered to %s because one or more recipients are offline");
4401 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4402 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4403 msg ? ":" : "",
4404 msg ? msg : "");
4405 sipe_present_err(sip, session, msg_tmp);
4406 g_free(msg_tmp2);
4407 g_free(msg_tmp);
4408 g_free(msg);
4412 static gboolean
4413 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4414 SIPE_UNUSED_PARAMETER struct transaction *trans)
4416 gboolean ret = TRUE;
4417 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4418 struct sip_session *session = sipe_session_find_im(sip, with);
4419 struct sip_dialog *dialog;
4420 gchar *cseq;
4421 char *key;
4422 struct queued_message *message;
4424 if (!session) {
4425 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4426 g_free(with);
4427 return FALSE;
4430 dialog = sipe_dialog_find(session, with);
4431 if (!dialog) {
4432 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4433 g_free(with);
4434 return FALSE;
4437 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4438 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4439 g_free(cseq);
4440 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4442 if (msg->response >= 400) {
4443 PurpleBuddy *pbuddy;
4444 const char *alias = with;
4445 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4446 int warning = -1;
4448 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4450 if (warn_hdr) {
4451 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4452 if (parts[0]) {
4453 warning = atoi(parts[0]);
4455 g_strfreev(parts);
4458 /* cancel file transfer as rejected by server */
4459 if (msg->response == 606 && /* Not acceptable all. */
4460 warning == 309 && /* Message contents not allowed by policy */
4461 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4463 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4464 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4465 sipe_utils_nameval_free(parsed_body);
4468 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4469 alias = purple_buddy_get_alias(pbuddy);
4472 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4474 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4475 if (msg->response == 408 || /* Request timeout */
4476 msg->response == 480 || /* Temporarily Unavailable */
4477 msg->response == 481) { /* Call/Transaction Does Not Exist */
4478 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4479 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4482 ret = FALSE;
4483 } else {
4484 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4485 if (message_id) {
4486 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4487 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4488 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4491 g_hash_table_remove(session->unconfirmed_messages, key);
4492 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4493 key, g_hash_table_size(session->unconfirmed_messages));
4496 g_free(key);
4497 g_free(with);
4499 if (ret) sipe_im_process_queue(sip, session);
4500 return ret;
4503 static gboolean
4504 sipe_is_election_finished(struct sip_session *session);
4506 static void
4507 sipe_election_result(struct sipe_account_data *sip,
4508 void *sess);
4510 static gboolean
4511 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4512 SIPE_UNUSED_PARAMETER struct transaction *trans)
4514 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4515 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4516 struct sip_dialog *dialog;
4517 struct sip_session *session;
4519 session = sipe_session_find_chat_by_callid(sip, callid);
4520 if (!session) {
4521 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4522 return FALSE;
4525 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4526 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4527 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
4528 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
4530 if (xn_request_rm_response) {
4531 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
4532 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
4534 dialog = sipe_dialog_find(session, with);
4535 if (!dialog) {
4536 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4537 sipe_xml_free(xn_action);
4538 return FALSE;
4541 if (allow && !g_strcasecmp(allow, "true")) {
4542 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4543 dialog->election_vote = 1;
4544 } else if (allow && !g_strcasecmp(allow, "false")) {
4545 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4546 dialog->election_vote = -1;
4549 if (sipe_is_election_finished(session)) {
4550 sipe_election_result(sip, session);
4553 } else if (xn_set_rm_response) {
4556 sipe_xml_free(xn_action);
4560 return TRUE;
4563 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4565 gchar *hdr;
4566 gchar *tmp;
4567 char *msgtext = NULL;
4568 const gchar *msgr = "";
4569 gchar *tmp2 = NULL;
4571 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4572 char *msgformat;
4573 gchar *msgr_value;
4575 sipe_parse_html(msg, &msgformat, &msgtext);
4576 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4578 msgr_value = sipmsg_get_msgr_string(msgformat);
4579 g_free(msgformat);
4580 if (msgr_value) {
4581 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4582 g_free(msgr_value);
4584 } else {
4585 msgtext = g_strdup(msg);
4588 tmp = get_contact(sip);
4589 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4590 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4591 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4592 if (content_type == NULL)
4593 content_type = "text/plain";
4595 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4596 g_free(tmp);
4597 g_free(tmp2);
4599 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4600 g_free(msgtext);
4601 g_free(hdr);
4605 void
4606 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4608 GSList *entry2 = session->outgoing_message_queue;
4609 while (entry2) {
4610 struct queued_message *msg = entry2->data;
4612 /* for multiparty chat or conference */
4613 if (session->is_multiparty || session->focus_uri) {
4614 gchar *who = sip_uri_self(sip);
4615 serv_got_chat_in(sip->gc, session->chat_id, who,
4616 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4617 g_free(who);
4620 SIPE_DIALOG_FOREACH {
4621 char *key;
4622 struct queued_message *message;
4624 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4626 message = g_new0(struct queued_message,1);
4627 message->body = g_strdup(msg->body);
4628 if (msg->content_type != NULL)
4629 message->content_type = g_strdup(msg->content_type);
4631 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4632 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4633 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4634 key, g_hash_table_size(session->unconfirmed_messages));
4635 g_free(key);
4637 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4638 } SIPE_DIALOG_FOREACH_END;
4640 entry2 = sipe_session_dequeue_message(session);
4644 static void
4645 sipe_refer_notify(struct sipe_account_data *sip,
4646 struct sip_session *session,
4647 const gchar *who,
4648 int status,
4649 const gchar *desc)
4651 gchar *hdr;
4652 gchar *body;
4653 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4655 hdr = g_strdup_printf(
4656 "Event: refer\r\n"
4657 "Subscription-State: %s\r\n"
4658 "Content-Type: message/sipfrag\r\n",
4659 status >= 200 ? "terminated" : "active");
4661 body = g_strdup_printf(
4662 "SIP/2.0 %d %s\r\n",
4663 status, desc);
4665 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4667 g_free(hdr);
4668 g_free(body);
4671 static gboolean
4672 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4674 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4675 struct sip_session *session;
4676 struct sip_dialog *dialog;
4677 char *cseq;
4678 char *key;
4679 struct queued_message *message;
4680 struct sipmsg *request_msg = trans->msg;
4682 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4683 gchar *referred_by;
4685 session = sipe_session_find_chat_by_callid(sip, callid);
4686 if (!session) {
4687 session = sipe_session_find_im(sip, with);
4689 if (!session) {
4690 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4691 g_free(with);
4692 return FALSE;
4695 dialog = sipe_dialog_find(session, with);
4696 if (!dialog) {
4697 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4698 g_free(with);
4699 return FALSE;
4702 sipe_dialog_parse(dialog, msg, TRUE);
4704 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4705 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4706 g_free(cseq);
4707 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4709 if (msg->response != 200) {
4710 PurpleBuddy *pbuddy;
4711 const char *alias = with;
4712 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4713 int warning = -1;
4715 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4717 if (warn_hdr) {
4718 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4719 if (parts[0]) {
4720 warning = atoi(parts[0]);
4722 g_strfreev(parts);
4725 /* cancel file transfer as rejected by server */
4726 if (msg->response == 606 && /* Not acceptable all. */
4727 warning == 309 && /* Message contents not allowed by policy */
4728 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4730 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4731 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4732 sipe_utils_nameval_free(parsed_body);
4735 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4736 alias = purple_buddy_get_alias(pbuddy);
4739 if (message) {
4740 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4741 } else {
4742 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4743 sipe_present_err(sip, session, tmp_msg);
4744 g_free(tmp_msg);
4747 sipe_dialog_remove(session, with);
4749 g_free(key);
4750 g_free(with);
4751 return FALSE;
4754 dialog->cseq = 0;
4755 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4756 dialog->outgoing_invite = NULL;
4757 dialog->is_established = TRUE;
4759 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4760 if (referred_by) {
4761 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4762 g_free(referred_by);
4765 /* add user to chat if it is a multiparty session */
4766 if (session->is_multiparty) {
4767 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4768 with, NULL,
4769 PURPLE_CBFLAGS_NONE, TRUE);
4772 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4773 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4774 sipe_session_dequeue_message(session);
4777 sipe_im_process_queue(sip, session);
4779 g_hash_table_remove(session->unconfirmed_messages, key);
4780 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4781 key, g_hash_table_size(session->unconfirmed_messages));
4783 g_free(key);
4784 g_free(with);
4785 return TRUE;
4789 void
4790 sipe_invite(struct sipe_account_data *sip,
4791 struct sip_session *session,
4792 const gchar *who,
4793 const gchar *msg_body,
4794 const gchar *msg_content_type,
4795 const gchar *referred_by,
4796 const gboolean is_triggered)
4798 gchar *hdr;
4799 gchar *to;
4800 gchar *contact;
4801 gchar *body;
4802 gchar *self;
4803 char *ms_text_format = NULL;
4804 gchar *roster_manager;
4805 gchar *end_points;
4806 gchar *referred_by_str;
4807 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4809 if (dialog && dialog->is_established) {
4810 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4811 return;
4814 if (!dialog) {
4815 dialog = sipe_dialog_add(session);
4816 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4817 dialog->with = g_strdup(who);
4820 if (!(dialog->ourtag)) {
4821 dialog->ourtag = gentag();
4824 to = sip_uri(who);
4826 if (msg_body) {
4827 char *msgtext = NULL;
4828 char *base64_msg;
4829 const gchar *msgr = "";
4830 char *key;
4831 struct queued_message *message;
4832 gchar *tmp = NULL;
4834 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4835 char *msgformat;
4836 gchar *msgr_value;
4838 sipe_parse_html(msg_body, &msgformat, &msgtext);
4839 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4841 msgr_value = sipmsg_get_msgr_string(msgformat);
4842 g_free(msgformat);
4843 if (msgr_value) {
4844 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4845 g_free(msgr_value);
4847 } else {
4848 msgtext = g_strdup(msg_body);
4851 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4852 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4853 msg_content_type ? msg_content_type : "text/plain",
4854 msgr,
4855 base64_msg);
4856 g_free(msgtext);
4857 g_free(tmp);
4858 g_free(base64_msg);
4860 message = g_new0(struct queued_message,1);
4861 message->body = g_strdup(msg_body);
4862 if (msg_content_type != NULL)
4863 message->content_type = g_strdup(msg_content_type);
4865 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4866 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4867 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4868 key, g_hash_table_size(session->unconfirmed_messages));
4869 g_free(key);
4872 contact = get_contact(sip);
4873 end_points = get_end_points(sip, session);
4874 self = sip_uri_self(sip);
4875 roster_manager = g_strdup_printf(
4876 "Roster-Manager: %s\r\n"
4877 "EndPoints: %s\r\n",
4878 self,
4879 end_points);
4880 referred_by_str = referred_by ?
4881 g_strdup_printf(
4882 "Referred-By: %s\r\n",
4883 referred_by)
4884 : g_strdup("");
4885 hdr = g_strdup_printf(
4886 "Supported: ms-sender\r\n"
4887 "%s"
4888 "%s"
4889 "%s"
4890 "%s"
4891 "Contact: %s\r\n%s"
4892 "Content-Type: application/sdp\r\n",
4893 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4894 referred_by_str,
4895 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4896 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4897 contact,
4898 ms_text_format ? ms_text_format : "");
4899 g_free(ms_text_format);
4900 g_free(self);
4902 body = g_strdup_printf(
4903 "v=0\r\n"
4904 "o=- 0 0 IN IP4 %s\r\n"
4905 "s=session\r\n"
4906 "c=IN IP4 %s\r\n"
4907 "t=0 0\r\n"
4908 "m=%s %d sip null\r\n"
4909 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4910 sipe_backend_network_ip_address(),
4911 sipe_backend_network_ip_address(),
4912 sip->ocs2007 ? "message" : "x-ms-message",
4913 sip->realport);
4915 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4916 to, to, hdr, body, dialog, process_invite_response);
4918 g_free(to);
4919 g_free(roster_manager);
4920 g_free(end_points);
4921 g_free(referred_by_str);
4922 g_free(body);
4923 g_free(hdr);
4924 g_free(contact);
4927 static void
4928 sipe_refer(struct sipe_account_data *sip,
4929 struct sip_session *session,
4930 const gchar *who)
4932 gchar *hdr;
4933 gchar *contact;
4934 gchar *epid = get_epid(sip);
4935 struct sip_dialog *dialog = sipe_dialog_find(session,
4936 session->roster_manager);
4937 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4939 contact = get_contact(sip);
4940 hdr = g_strdup_printf(
4941 "Contact: %s\r\n"
4942 "Refer-to: <%s>\r\n"
4943 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4944 "Require: com.microsoft.rtc-multiparty\r\n",
4945 contact,
4946 who,
4947 sip->username,
4948 ourtag ? ";tag=" : "",
4949 ourtag ? ourtag : "",
4950 epid);
4951 g_free(epid);
4953 send_sip_request(sip->gc, "REFER",
4954 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4956 g_free(hdr);
4957 g_free(contact);
4960 static void
4961 sipe_send_election_request_rm(struct sipe_account_data *sip,
4962 struct sip_dialog *dialog,
4963 int bid)
4965 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4967 gchar *body = g_strdup_printf(
4968 "<?xml version=\"1.0\"?>\r\n"
4969 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4970 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4971 sip->username, bid);
4973 send_sip_request(sip->gc, "INFO",
4974 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4976 g_free(body);
4979 static void
4980 sipe_send_election_set_rm(struct sipe_account_data *sip,
4981 struct sip_dialog *dialog)
4983 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4985 gchar *body = g_strdup_printf(
4986 "<?xml version=\"1.0\"?>\r\n"
4987 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4988 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4989 sip->username);
4991 send_sip_request(sip->gc, "INFO",
4992 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4994 g_free(body);
4997 static void
4998 sipe_session_close(struct sipe_account_data *sip,
4999 struct sip_session * session)
5001 if (session && session->focus_uri) {
5002 sipe_conf_immcu_closed(sip, session);
5003 conf_session_close(sip, session);
5006 if (session) {
5007 SIPE_DIALOG_FOREACH {
5008 /* @TODO slow down BYE message sending rate */
5009 /* @see single subscription code */
5010 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
5011 } SIPE_DIALOG_FOREACH_END;
5013 sipe_session_remove(sip, session);
5017 static void
5018 sipe_session_close_all(struct sipe_account_data *sip)
5020 GSList *entry;
5021 while ((entry = sip->sessions) != NULL) {
5022 sipe_session_close(sip, entry->data);
5026 static void
5027 sipe_convo_closed(PurpleConnection * gc, const char *who)
5029 struct sipe_account_data *sip = gc->proto_data;
5031 SIPE_DEBUG_INFO("conversation with %s closed", who);
5032 sipe_session_close(sip, sipe_session_find_im(sip, who));
5035 static void
5036 sipe_chat_invite(PurpleConnection *gc, int id,
5037 SIPE_UNUSED_PARAMETER const char *message,
5038 const char *name)
5040 sipe_chat_create(gc->proto_data, id, name);
5043 static void
5044 sipe_chat_leave (PurpleConnection *gc, int id)
5046 struct sipe_account_data *sip = gc->proto_data;
5047 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
5049 sipe_session_close(sip, session);
5052 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
5053 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
5055 struct sipe_account_data *sip = gc->proto_data;
5056 struct sip_session *session;
5057 struct sip_dialog *dialog;
5058 gchar *uri = sip_uri(who);
5060 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
5062 session = sipe_session_find_or_add_im(sip, uri);
5063 dialog = sipe_dialog_find(session, uri);
5065 // Queue the message
5066 sipe_session_enqueue_message(session, what, NULL);
5068 if (dialog && !dialog->outgoing_invite) {
5069 sipe_im_process_queue(sip, session);
5070 } else if (!dialog || !dialog->outgoing_invite) {
5071 // Need to send the INVITE to get the outgoing dialog setup
5072 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
5075 g_free(uri);
5076 return 1;
5079 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
5080 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
5082 struct sipe_account_data *sip = gc->proto_data;
5083 struct sip_session *session;
5085 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
5087 session = sipe_session_find_chat_by_id(sip, id);
5089 // Queue the message
5090 if (session && session->dialogs) {
5091 sipe_session_enqueue_message(session,what,NULL);
5092 sipe_im_process_queue(sip, session);
5093 } else if (sip) {
5094 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
5095 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
5097 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
5098 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
5100 if (sip->ocs2007) {
5101 struct sip_session *session = sipe_session_add_chat(sip);
5103 session->is_multiparty = FALSE;
5104 session->focus_uri = g_strdup(proto_chat_id);
5105 sipe_session_enqueue_message(session, what, NULL);
5106 sipe_invite_conf_focus(sip, session);
5110 return 1;
5113 /* End IM Session (INVITE and MESSAGE methods) */
5115 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
5117 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
5118 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5119 gchar *from;
5120 struct sip_session *session;
5122 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
5124 /* Call Control protocol */
5125 if (g_str_has_prefix(contenttype, "application/csta+xml"))
5127 process_incoming_info_csta(sip, msg);
5128 return;
5131 from = parse_from(sipmsg_find_header(msg, "From"));
5132 session = sipe_session_find_chat_by_callid(sip, callid);
5133 if (!session) {
5134 session = sipe_session_find_im(sip, from);
5136 if (!session) {
5137 g_free(from);
5138 return;
5141 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
5143 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
5144 const sipe_xml *xn_request_rm = sipe_xml_child(xn_action, "RequestRM");
5145 const sipe_xml *xn_set_rm = sipe_xml_child(xn_action, "SetRM");
5147 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
5149 if (xn_request_rm) {
5150 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
5151 int bid = sipe_xml_int_attribute(xn_request_rm, "bid", 0);
5152 gchar *body = g_strdup_printf(
5153 "<?xml version=\"1.0\"?>\r\n"
5154 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5155 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
5156 sip->username,
5157 session->bid < bid ? "true" : "false");
5158 send_sip_response(sip->gc, msg, 200, "OK", body);
5159 g_free(body);
5160 } else if (xn_set_rm) {
5161 gchar *body;
5162 const char *rm = sipe_xml_attribute(xn_set_rm, "uri");
5163 g_free(session->roster_manager);
5164 session->roster_manager = g_strdup(rm);
5166 body = g_strdup_printf(
5167 "<?xml version=\"1.0\"?>\r\n"
5168 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5169 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
5170 sip->username);
5171 send_sip_response(sip->gc, msg, 200, "OK", body);
5172 g_free(body);
5174 sipe_xml_free(xn_action);
5177 else
5179 /* looks like purple lacks typing notification for chat */
5180 if (!session->is_multiparty && !session->focus_uri) {
5181 sipe_xml *xn_keyboard_activity = sipe_xml_parse(msg->body, msg->bodylen);
5182 const char *status = sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity, "status"),
5183 "status");
5184 if (sipe_strequal(status, "type")) {
5185 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
5186 } else if (sipe_strequal(status, "idle")) {
5187 serv_got_typing_stopped(sip->gc, from);
5189 sipe_xml_free(xn_keyboard_activity);
5192 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5194 g_free(from);
5197 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
5199 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5200 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5201 struct sip_session *session;
5202 struct sip_dialog *dialog;
5204 /* collect dialog identification
5205 * we need callid, ourtag and theirtag to unambiguously identify dialog
5207 /* take data before 'msg' will be modified by send_sip_response */
5208 dialog = g_new0(struct sip_dialog, 1);
5209 dialog->callid = g_strdup(callid);
5210 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
5211 dialog->with = g_strdup(from);
5212 sipe_dialog_parse(dialog, msg, FALSE);
5214 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5216 session = sipe_session_find_chat_by_callid(sip, callid);
5217 if (!session) {
5218 session = sipe_session_find_im(sip, from);
5220 if (!session) {
5221 sipe_dialog_free(dialog);
5222 g_free(from);
5223 return;
5226 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
5227 g_free(session->roster_manager);
5228 session->roster_manager = NULL;
5231 /* This what BYE is essentially for - terminating dialog */
5232 sipe_dialog_remove_3(session, dialog);
5233 sipe_dialog_free(dialog);
5234 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
5235 sipe_conf_immcu_closed(sip, session);
5236 } else if (session->is_multiparty) {
5237 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
5240 g_free(from);
5243 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
5245 gchar *self = sip_uri_self(sip);
5246 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5247 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
5248 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
5249 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
5250 struct sip_session *session;
5251 struct sip_dialog *dialog;
5253 session = sipe_session_find_chat_by_callid(sip, callid);
5254 dialog = sipe_dialog_find(session, from);
5256 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
5257 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
5258 } else {
5259 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
5261 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
5264 g_free(self);
5265 g_free(from);
5266 g_free(refer_to);
5267 g_free(referred_by);
5270 static unsigned int
5271 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
5273 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
5274 struct sip_session *session;
5275 struct sip_dialog *dialog;
5277 if (state == PURPLE_NOT_TYPING)
5278 return 0;
5280 session = sipe_session_find_im(sip, who);
5281 dialog = sipe_dialog_find(session, who);
5283 if (session && dialog && dialog->is_established) {
5284 send_sip_request(gc, "INFO", who, who,
5285 "Content-Type: application/xml\r\n",
5286 SIPE_SEND_TYPING, dialog, NULL);
5288 return SIPE_TYPING_SEND_TIMEOUT;
5291 static gboolean resend_timeout(struct sipe_account_data *sip)
5293 GSList *tmp = sip->transactions;
5294 time_t currtime = time(NULL);
5295 while (tmp) {
5296 struct transaction *trans = tmp->data;
5297 tmp = tmp->next;
5298 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime-trans->time);
5299 if ((currtime - trans->time > 5) && trans->retries >= 1) {
5300 /* TODO 408 */
5301 } else {
5302 if ((currtime - trans->time > 2) && trans->retries == 0) {
5303 trans->retries++;
5304 sendout_sipmsg(sip, trans->msg);
5308 return TRUE;
5311 static void do_reauthenticate_cb(struct sipe_account_data *sip,
5312 SIPE_UNUSED_PARAMETER void *unused)
5314 /* register again when security token expires */
5315 /* we have to start a new authentication as the security token
5316 * is almost expired by sending a not signed REGISTER message */
5317 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5318 sipe_auth_free(&sip->registrar);
5319 sipe_auth_free(&sip->proxy);
5320 sip->registerstatus = 0;
5321 do_register(sip);
5322 sip->reauthenticate_set = FALSE;
5325 static gboolean
5326 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
5327 struct sipmsg *msg,
5328 GSList *parsed_body)
5330 gboolean found = FALSE;
5332 if (parsed_body) {
5333 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
5335 if (sipe_strequal(invitation_command, "INVITE")) {
5336 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
5337 found = TRUE;
5338 } else if (sipe_strequal(invitation_command, "CANCEL")) {
5339 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
5340 found = TRUE;
5341 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
5342 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
5343 found = TRUE;
5346 return found;
5349 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
5351 gchar *from;
5352 const gchar *contenttype;
5353 gboolean found = FALSE;
5355 from = parse_from(sipmsg_find_header(msg, "From"));
5357 if (!from) return;
5359 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
5361 contenttype = sipmsg_find_header(msg, "Content-Type");
5362 if (g_str_has_prefix(contenttype, "text/plain")
5363 || g_str_has_prefix(contenttype, "text/html")
5364 || g_str_has_prefix(contenttype, "multipart/related")
5365 || g_str_has_prefix(contenttype, "multipart/alternative"))
5367 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5368 gchar *html = get_html_message(contenttype, msg->body);
5370 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5371 if (!session) {
5372 session = sipe_session_find_im(sip, from);
5375 if (session && session->focus_uri) { /* a conference */
5376 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
5377 gchar *sender = parse_from(tmp);
5378 g_free(tmp);
5379 serv_got_chat_in(sip->gc, session->chat_id, sender,
5380 PURPLE_MESSAGE_RECV, html, time(NULL));
5381 g_free(sender);
5382 } else if (session && session->is_multiparty) { /* a multiparty chat */
5383 serv_got_chat_in(sip->gc, session->chat_id, from,
5384 PURPLE_MESSAGE_RECV, html, time(NULL));
5385 } else {
5386 serv_got_im(sip->gc, from, html, 0, time(NULL));
5388 g_free(html);
5389 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5390 found = TRUE;
5392 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
5393 sipe_xml *isc = sipe_xml_parse(msg->body, msg->bodylen);
5394 const sipe_xml *state;
5395 gchar *statedata;
5397 if (!isc) {
5398 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5399 g_free(from);
5400 return;
5403 state = sipe_xml_child(isc, "state");
5405 if (!state) {
5406 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5407 sipe_xml_free(isc);
5408 g_free(from);
5409 return;
5412 statedata = sipe_xml_data(state);
5413 if (statedata) {
5414 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
5415 else serv_got_typing_stopped(sip->gc, from);
5417 g_free(statedata);
5419 sipe_xml_free(isc);
5420 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5421 found = TRUE;
5422 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5423 GSList *body = sipe_ft_parse_msg_body(msg->body);
5424 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5425 sipe_utils_nameval_free(body);
5426 if (found) {
5427 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5430 if (!found) {
5431 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5432 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5433 if (!session) {
5434 session = sipe_session_find_im(sip, from);
5436 if (session) {
5437 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5438 from);
5439 sipe_present_err(sip, session, errmsg);
5440 g_free(errmsg);
5443 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5444 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5446 g_free(from);
5449 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5451 gchar *body;
5452 gchar *newTag;
5453 const gchar *oldHeader;
5454 gchar *newHeader;
5455 gboolean is_multiparty = FALSE;
5456 gboolean is_triggered = FALSE;
5457 gboolean was_multiparty = TRUE;
5458 gboolean just_joined = FALSE;
5459 gchar *from;
5460 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5461 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5462 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5463 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5464 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5465 GSList *end_points = NULL;
5466 char *tmp = NULL;
5467 struct sip_session *session;
5468 const gchar *ms_text_format;
5470 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5471 g_free(tmp);
5473 /* Invitation to join conference */
5474 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5475 process_incoming_invite_conf(sip, msg);
5476 return;
5479 /* Only accept text invitations */
5480 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5481 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5482 return;
5485 // TODO There *must* be a better way to clean up the To header to add a tag...
5486 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5487 oldHeader = sipmsg_find_header(msg, "To");
5488 newTag = gentag();
5489 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5490 sipmsg_remove_header_now(msg, "To");
5491 sipmsg_add_header_now(msg, "To", newHeader);
5492 g_free(newHeader);
5494 if (end_points_hdr) {
5495 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5497 if (g_slist_length(end_points) > 2) {
5498 is_multiparty = TRUE;
5501 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5502 is_triggered = TRUE;
5503 is_multiparty = TRUE;
5506 session = sipe_session_find_chat_by_callid(sip, callid);
5507 /* Convert to multiparty */
5508 if (session && is_multiparty && !session->is_multiparty) {
5509 g_free(session->with);
5510 session->with = NULL;
5511 was_multiparty = FALSE;
5512 session->is_multiparty = TRUE;
5513 session->chat_id = rand();
5516 if (!session && is_multiparty) {
5517 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5519 /* IM session */
5520 from = parse_from(sipmsg_find_header(msg, "From"));
5521 if (!session) {
5522 session = sipe_session_find_or_add_im(sip, from);
5525 if (session) {
5526 g_free(session->callid);
5527 session->callid = g_strdup(callid);
5529 session->is_multiparty = is_multiparty;
5530 if (roster_manager) {
5531 session->roster_manager = g_strdup(roster_manager);
5535 if (is_multiparty && end_points) {
5536 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5537 GSList *entry = end_points;
5538 while (entry) {
5539 struct sip_dialog *dialog;
5540 struct sipendpoint *end_point = entry->data;
5541 entry = entry->next;
5543 if (!g_strcasecmp(from, end_point->contact) ||
5544 !g_strcasecmp(to, end_point->contact))
5545 continue;
5547 dialog = sipe_dialog_find(session, end_point->contact);
5548 if (dialog) {
5549 g_free(dialog->theirepid);
5550 dialog->theirepid = end_point->epid;
5551 end_point->epid = NULL;
5552 } else {
5553 dialog = sipe_dialog_add(session);
5555 dialog->callid = g_strdup(session->callid);
5556 dialog->with = end_point->contact;
5557 end_point->contact = NULL;
5558 dialog->theirepid = end_point->epid;
5559 end_point->epid = NULL;
5561 just_joined = TRUE;
5563 /* send triggered INVITE */
5564 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5567 g_free(to);
5570 if (end_points) {
5571 GSList *entry = end_points;
5572 while (entry) {
5573 struct sipendpoint *end_point = entry->data;
5574 entry = entry->next;
5575 g_free(end_point->contact);
5576 g_free(end_point->epid);
5577 g_free(end_point);
5579 g_slist_free(end_points);
5582 if (session) {
5583 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5584 if (dialog) {
5585 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5586 sipe_dialog_parse_routes(dialog, msg, FALSE);
5587 } else {
5588 dialog = sipe_dialog_add(session);
5590 dialog->callid = g_strdup(session->callid);
5591 dialog->with = g_strdup(from);
5592 sipe_dialog_parse(dialog, msg, FALSE);
5594 if (!dialog->ourtag) {
5595 dialog->ourtag = newTag;
5596 newTag = NULL;
5599 just_joined = TRUE;
5601 } else {
5602 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5604 g_free(newTag);
5606 if (is_multiparty && !session->conv) {
5607 gchar *chat_title = sipe_chat_get_name(callid);
5608 gchar *self = sip_uri_self(sip);
5609 /* create prpl chat */
5610 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5611 session->chat_title = g_strdup(chat_title);
5612 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5613 /* add self */
5614 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5615 self, NULL,
5616 PURPLE_CBFLAGS_NONE, FALSE);
5617 g_free(chat_title);
5618 g_free(self);
5621 if (is_multiparty && !was_multiparty) {
5622 /* add current IM counterparty to chat */
5623 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5624 sipe_dialog_first(session)->with, NULL,
5625 PURPLE_CBFLAGS_NONE, FALSE);
5628 /* add inviting party to chat */
5629 if (just_joined && session->conv) {
5630 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5631 from, NULL,
5632 PURPLE_CBFLAGS_NONE, TRUE);
5635 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5637 /* This used only in 2005 official client, not 2007 or Reuters.
5638 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5639 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5641 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5642 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5643 if (is_multiparty ||
5644 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5646 if (ms_text_format) {
5647 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5649 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5650 if (tmp) {
5651 gsize len;
5652 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5654 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5656 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5657 sipe_utils_nameval_free(parsed_body);
5658 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5660 g_free(tmp);
5662 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5664 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5665 gchar *html = get_html_message(ms_text_format, NULL);
5666 if (html) {
5667 if (is_multiparty) {
5668 serv_got_chat_in(sip->gc, session->chat_id, from,
5669 PURPLE_MESSAGE_RECV, html, time(NULL));
5670 } else {
5671 serv_got_im(sip->gc, from, html, 0, time(NULL));
5673 g_free(html);
5674 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5680 g_free(from);
5682 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5683 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5684 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5686 body = g_strdup_printf(
5687 "v=0\r\n"
5688 "o=- 0 0 IN IP4 %s\r\n"
5689 "s=session\r\n"
5690 "c=IN IP4 %s\r\n"
5691 "t=0 0\r\n"
5692 "m=%s %d sip sip:%s\r\n"
5693 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5694 sipe_backend_network_ip_address(),
5695 sipe_backend_network_ip_address(),
5696 sip->ocs2007 ? "message" : "x-ms-message",
5697 sip->realport,
5698 sip->username);
5699 send_sip_response(sip->gc, msg, 200, "OK", body);
5700 g_free(body);
5703 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5705 gchar *body;
5707 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5708 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5709 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5711 body = g_strdup_printf(
5712 "v=0\r\n"
5713 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5714 "s=session\r\n"
5715 "c=IN IP4 0.0.0.0\r\n"
5716 "t=0 0\r\n"
5717 "m=%s %d sip sip:%s\r\n"
5718 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5719 sip->ocs2007 ? "message" : "x-ms-message",
5720 sip->realport,
5721 sip->username);
5722 send_sip_response(sip->gc, msg, 200, "OK", body);
5723 g_free(body);
5726 static const char*
5727 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5729 const char *res = "NTLM";
5730 #ifdef HAVE_LIBKRB5
5731 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5732 res = "Kerberos";
5734 #else
5735 (void) sip; /* make compiler happy */
5736 #endif
5737 return res;
5740 static void sipe_connection_cleanup(struct sipe_account_data *);
5741 static void create_connection(struct sipe_account_data *, gchar *, int);
5743 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5744 SIPE_UNUSED_PARAMETER struct transaction *trans)
5746 gchar *tmp;
5747 const gchar *expires_header;
5748 int expires, i;
5749 GSList *hdr = msg->headers;
5750 struct sipnameval *elem;
5752 expires_header = sipmsg_find_header(msg, "Expires");
5753 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5754 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5756 switch (msg->response) {
5757 case 200:
5758 if (expires == 0) {
5759 sip->registerstatus = 0;
5760 } else {
5761 const gchar *contact_hdr;
5762 gchar *gruu = NULL;
5763 gchar *epid;
5764 gchar *uuid;
5765 gchar *timeout;
5766 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5767 const char *auth_scheme;
5769 if (!sip->reregister_set) {
5770 gchar *action_name = g_strdup_printf("<%s>", "registration");
5771 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
5772 g_free(action_name);
5773 sip->reregister_set = TRUE;
5776 sip->registerstatus = 3;
5778 if (server_hdr && !sip->server_version) {
5779 sip->server_version = g_strdup(server_hdr);
5780 g_free(default_ua);
5781 default_ua = NULL;
5784 auth_scheme = sipe_get_auth_scheme_name(sip);
5785 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5787 if (tmp) {
5788 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5789 fill_auth(tmp, &sip->registrar);
5792 if (!sip->reauthenticate_set) {
5793 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5794 guint reauth_timeout;
5795 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5796 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5797 reauth_timeout = sip->registrar.expires - 300;
5798 } else {
5799 /* NTLM: we have to reauthenticate as our security token expires
5800 after eight hours (be five minutes early) */
5801 reauth_timeout = (8 * 3600) - 300;
5803 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
5804 g_free(action_name);
5805 sip->reauthenticate_set = TRUE;
5808 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5810 epid = get_epid(sip);
5811 uuid = generateUUIDfromEPID(epid);
5812 g_free(epid);
5814 // There can be multiple Contact headers (one per location where the user is logged in) so
5815 // make sure to only get the one for this uuid
5816 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5817 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5818 if (valid_contact) {
5819 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5820 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5821 g_free(valid_contact);
5822 break;
5823 } else {
5824 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5827 g_free(uuid);
5829 g_free(sip->contact);
5830 if(gruu) {
5831 sip->contact = g_strdup_printf("<%s>", gruu);
5832 g_free(gruu);
5833 } else {
5834 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5835 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, sipe_backend_network_ip_address(), TRANSPORT_DESCRIPTOR);
5837 sip->ocs2007 = FALSE;
5838 sip->batched_support = FALSE;
5840 while(hdr)
5842 elem = hdr->data;
5843 if (sipe_strcase_equal(elem->name, "Supported")) {
5844 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5845 /* We interpret this as OCS2007+ indicator */
5846 sip->ocs2007 = TRUE;
5847 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5849 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5850 sip->batched_support = TRUE;
5851 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5854 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5855 gchar **caps = g_strsplit(elem->value,",",0);
5856 i = 0;
5857 while (caps[i]) {
5858 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5859 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5860 i++;
5862 g_strfreev(caps);
5864 hdr = g_slist_next(hdr);
5867 /* rejoin open chats to be able to use them by continue to send messages */
5868 purple_conversation_foreach(sipe_rejoin_chat);
5870 /* subscriptions */
5871 if (!sip->subscribed) { //do it just once, not every re-register
5873 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5874 (GCompareFunc)g_ascii_strcasecmp)) {
5875 sipe_subscribe_roaming_contacts(sip);
5878 /* For 2007+ it does not make sence to subscribe to:
5879 * vnd-microsoft-roaming-ACL
5880 * vnd-microsoft-provisioning (not v2)
5881 * presence.wpending
5882 * These are for backward compatibility.
5884 if (sip->ocs2007)
5886 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5887 (GCompareFunc)g_ascii_strcasecmp)) {
5888 sipe_subscribe_roaming_self(sip);
5890 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5891 (GCompareFunc)g_ascii_strcasecmp)) {
5892 sipe_subscribe_roaming_provisioning_v2(sip);
5895 /* For 2005- servers */
5896 else
5898 //sipe_options_request(sip, sip->sipdomain);
5900 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5901 (GCompareFunc)g_ascii_strcasecmp)) {
5902 sipe_subscribe_roaming_acl(sip);
5904 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5905 (GCompareFunc)g_ascii_strcasecmp)) {
5906 sipe_subscribe_roaming_provisioning(sip);
5908 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5909 (GCompareFunc)g_ascii_strcasecmp)) {
5910 sipe_subscribe_presence_wpending(sip, msg);
5913 /* For 2007+ we publish our initial statuses and calendar data only after
5914 * received our existing publications in sipe_process_roaming_self()
5915 * Only in this case we know versions of current publications made
5916 * on our behalf.
5918 /* For 2005- we publish our initial statuses only after
5919 * received our existing UserInfo data in response to
5920 * self subscription.
5921 * Only in this case we won't override existing UserInfo data
5922 * set earlier or by other client on our behalf.
5926 sip->subscribed = TRUE;
5929 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5930 "timeout=", ";", NULL);
5931 if (timeout != NULL) {
5932 sscanf(timeout, "%u", &sip->keepalive_timeout);
5933 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5934 sip->keepalive_timeout);
5935 g_free(timeout);
5938 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5940 break;
5941 case 301:
5943 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5945 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5946 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5947 gchar **tmp;
5948 gchar *hostname;
5949 int port = 0;
5950 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
5951 int i = 1;
5953 tmp = g_strsplit(parts[0], ":", 0);
5954 hostname = g_strdup(tmp[0]);
5955 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5956 g_strfreev(tmp);
5958 while (parts[i]) {
5959 tmp = g_strsplit(parts[i], "=", 0);
5960 if (tmp[1]) {
5961 if (g_strcasecmp("transport", tmp[0]) == 0) {
5962 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5963 transport = SIPE_TRANSPORT_TCP;
5964 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
5965 transport = SIPE_TRANSPORT_UDP;
5969 g_strfreev(tmp);
5970 i++;
5972 g_strfreev(parts);
5974 /* Close old connection */
5975 sipe_connection_cleanup(sip);
5977 /* Create new connection */
5978 sip->transport = transport;
5979 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5980 hostname, port, TRANSPORT_DESCRIPTOR);
5981 create_connection(sip, hostname, port);
5983 g_free(redirect);
5985 break;
5986 case 401:
5987 if (sip->registerstatus != 2) {
5988 const char *auth_scheme;
5989 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5990 if (sip->registrar.retries > 3) {
5991 sip->gc->wants_to_die = TRUE;
5992 purple_connection_error(sip->gc, _("Authentication failed"));
5993 return TRUE;
5996 auth_scheme = sipe_get_auth_scheme_name(sip);
5997 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5999 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
6000 if (!tmp) {
6001 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
6002 sip->gc->wants_to_die = TRUE;
6003 purple_connection_error(sip->gc, tmp2);
6004 g_free(tmp2);
6005 return TRUE;
6007 fill_auth(tmp, &sip->registrar);
6008 sip->registerstatus = 2;
6009 if (sip->account->disconnecting) {
6010 do_register_exp(sip, 0);
6011 } else {
6012 do_register(sip);
6015 break;
6016 case 403:
6018 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
6019 gchar **reason = NULL;
6020 gchar *warning;
6021 if (diagnostics != NULL) {
6022 /* Example header:
6023 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
6025 reason = g_strsplit(diagnostics, "\"", 0);
6027 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
6028 (reason && reason[1]) ? reason[1] : _("no reason given"));
6029 g_strfreev(reason);
6031 sip->gc->wants_to_die = TRUE;
6032 purple_connection_error(sip->gc, warning);
6033 g_free(warning);
6034 return TRUE;
6036 break;
6037 case 404:
6039 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
6040 gchar *reason = NULL;
6041 gchar *warning;
6042 if (diagnostics != NULL) {
6043 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
6045 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
6046 diagnostics ? (reason ? reason : _("no reason given")) :
6047 _("SIP is either not enabled for the destination URI or it does not exist"));
6048 g_free(reason);
6050 sip->gc->wants_to_die = TRUE;
6051 purple_connection_error(sip->gc, warning);
6052 g_free(warning);
6053 return TRUE;
6055 break;
6056 case 503:
6057 case 504: /* Server time-out */
6059 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
6060 gchar *reason = NULL;
6061 gchar *warning;
6062 if (diagnostics != NULL) {
6063 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
6065 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
6066 g_free(reason);
6068 sip->gc->wants_to_die = TRUE;
6069 purple_connection_error(sip->gc, warning);
6070 g_free(warning);
6071 return TRUE;
6073 break;
6075 return TRUE;
6079 * Returns 2005-style activity and Availability.
6081 * @param status Sipe statis id.
6083 static void
6084 sipe_get_act_avail_by_status_2005(const char *status,
6085 int *activity,
6086 int *availability)
6088 int avail = 300; /* online */
6089 int act = 400; /* Available */
6091 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
6092 act = 100;
6093 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
6094 // act = 150;
6095 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
6096 act = 300;
6097 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
6098 act = 400;
6099 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
6100 // act = 500;
6101 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
6102 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
6103 act = 600;
6104 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
6105 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
6106 avail = 0; /* offline */
6107 act = 100;
6108 } else {
6109 act = 400; /* Available */
6112 if (activity) *activity = act;
6113 if (availability) *availability = avail;
6117 * [MS-SIP] 2.2.1
6119 * @param activity 2005 aggregated activity. Ex.: 600
6120 * @param availablity 2005 aggregated availablity. Ex.: 300
6122 static const char *
6123 sipe_get_status_by_act_avail_2005(const int activity,
6124 const int availablity,
6125 char **activity_desc)
6127 const char *status_id = NULL;
6128 const char *act = NULL;
6130 if (activity < 150) {
6131 status_id = SIPE_STATUS_ID_AWAY;
6132 } else if (activity < 200) {
6133 //status_id = SIPE_STATUS_ID_LUNCH;
6134 status_id = SIPE_STATUS_ID_AWAY;
6135 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
6136 } else if (activity < 300) {
6137 //status_id = SIPE_STATUS_ID_IDLE;
6138 status_id = SIPE_STATUS_ID_AWAY;
6139 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6140 } else if (activity < 400) {
6141 status_id = SIPE_STATUS_ID_BRB;
6142 } else if (activity < 500) {
6143 status_id = SIPE_STATUS_ID_AVAILABLE;
6144 } else if (activity < 600) {
6145 //status_id = SIPE_STATUS_ID_ON_PHONE;
6146 status_id = SIPE_STATUS_ID_BUSY;
6147 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
6148 } else if (activity < 700) {
6149 status_id = SIPE_STATUS_ID_BUSY;
6150 } else if (activity < 800) {
6151 status_id = SIPE_STATUS_ID_AWAY;
6152 } else {
6153 status_id = SIPE_STATUS_ID_AVAILABLE;
6156 if (availablity < 100)
6157 status_id = SIPE_STATUS_ID_OFFLINE;
6159 if (activity_desc && act) {
6160 g_free(*activity_desc);
6161 *activity_desc = g_strdup(act);
6164 return status_id;
6168 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
6170 static const char*
6171 sipe_get_status_by_availability(int avail,
6172 char** activity_desc)
6174 const char *status;
6175 const char *act = NULL;
6177 if (avail < 3000) {
6178 status = SIPE_STATUS_ID_OFFLINE;
6179 } else if (avail < 4500) {
6180 status = SIPE_STATUS_ID_AVAILABLE;
6181 } else if (avail < 6000) {
6182 //status = SIPE_STATUS_ID_IDLE;
6183 status = SIPE_STATUS_ID_AVAILABLE;
6184 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
6185 } else if (avail < 7500) {
6186 status = SIPE_STATUS_ID_BUSY;
6187 } else if (avail < 9000) {
6188 //status = SIPE_STATUS_ID_BUSYIDLE;
6189 status = SIPE_STATUS_ID_BUSY;
6190 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
6191 } else if (avail < 12000) {
6192 status = SIPE_STATUS_ID_DND;
6193 } else if (avail < 15000) {
6194 status = SIPE_STATUS_ID_BRB;
6195 } else if (avail < 18000) {
6196 status = SIPE_STATUS_ID_AWAY;
6197 } else {
6198 status = SIPE_STATUS_ID_OFFLINE;
6201 if (activity_desc && act) {
6202 g_free(*activity_desc);
6203 *activity_desc = g_strdup(act);
6206 return status;
6210 * Returns 2007-style availability value
6212 * @param sipe_status_id (in)
6213 * @param activity_token (out) Must be g_free()'d after use if consumed.
6215 static int
6216 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
6218 int availability;
6219 sipe_activity activity = SIPE_ACTIVITY_UNSET;
6221 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
6222 availability = 15500;
6223 if (!activity_token || !(*activity_token)) {
6224 activity = SIPE_ACTIVITY_AWAY;
6226 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
6227 availability = 12500;
6228 activity = SIPE_ACTIVITY_BRB;
6229 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
6230 availability = 9500;
6231 activity = SIPE_ACTIVITY_DND;
6232 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
6233 availability = 6500;
6234 if (!activity_token || !(*activity_token)) {
6235 activity = SIPE_ACTIVITY_BUSY;
6237 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
6238 availability = 3500;
6239 activity = SIPE_ACTIVITY_ONLINE;
6240 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
6241 availability = 0;
6242 } else {
6243 // Offline or invisible
6244 availability = 18500;
6245 activity = SIPE_ACTIVITY_OFFLINE;
6248 if (activity_token) {
6249 *activity_token = g_strdup(sipe_activity_map[activity].token);
6251 return availability;
6254 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
6256 const char *uri;
6257 sipe_xml *xn_categories;
6258 const sipe_xml *xn_category;
6259 const char *status = NULL;
6260 gboolean do_update_status = FALSE;
6261 gboolean has_note_cleaned = FALSE;
6262 gboolean has_free_busy_cleaned = FALSE;
6264 xn_categories = sipe_xml_parse(data, len);
6265 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
6267 for (xn_category = sipe_xml_child(xn_categories, "category");
6268 xn_category ;
6269 xn_category = sipe_xml_twin(xn_category) )
6271 const sipe_xml *xn_node;
6272 const char *tmp;
6273 const char *attrVar = sipe_xml_attribute(xn_category, "name");
6274 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
6275 sipe_utils_str_to_time(tmp) : 0;
6277 /* contactCard */
6278 if (sipe_strequal(attrVar, "contactCard"))
6280 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
6282 if (card) {
6283 const sipe_xml *node;
6284 /* identity - Display Name and email */
6285 node = sipe_xml_child(card, "identity");
6286 if (node) {
6287 char* display_name = sipe_xml_data(
6288 sipe_xml_child(node, "name/displayName"));
6289 char* email = sipe_xml_data(
6290 sipe_xml_child(node, "email"));
6292 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6293 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6295 g_free(display_name);
6296 g_free(email);
6298 /* company */
6299 node = sipe_xml_child(card, "company");
6300 if (node) {
6301 char* company = sipe_xml_data(node);
6302 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
6303 g_free(company);
6305 /* department */
6306 node = sipe_xml_child(card, "department");
6307 if (node) {
6308 char* department = sipe_xml_data(node);
6309 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
6310 g_free(department);
6312 /* title */
6313 node = sipe_xml_child(card, "title");
6314 if (node) {
6315 char* title = sipe_xml_data(node);
6316 sipe_update_user_info(sip, uri, TITLE_PROP, title);
6317 g_free(title);
6319 /* office */
6320 node = sipe_xml_child(card, "office");
6321 if (node) {
6322 char* office = sipe_xml_data(node);
6323 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
6324 g_free(office);
6326 /* site (url) */
6327 node = sipe_xml_child(card, "url");
6328 if (node) {
6329 char* site = sipe_xml_data(node);
6330 sipe_update_user_info(sip, uri, SITE_PROP, site);
6331 g_free(site);
6333 /* phone */
6334 for (node = sipe_xml_child(card, "phone");
6335 node;
6336 node = sipe_xml_twin(node))
6338 const char *phone_type = sipe_xml_attribute(node, "type");
6339 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
6340 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
6342 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
6344 g_free(phone);
6345 g_free(phone_display_string);
6347 /* address */
6348 for (node = sipe_xml_child(card, "address");
6349 node;
6350 node = sipe_xml_twin(node))
6352 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
6353 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
6354 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
6355 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
6356 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
6357 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
6359 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
6360 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
6361 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
6362 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
6363 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
6365 g_free(street);
6366 g_free(city);
6367 g_free(state);
6368 g_free(zipcode);
6369 g_free(country_code);
6371 break;
6376 /* note */
6377 else if (sipe_strequal(attrVar, "note"))
6379 if (uri) {
6380 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
6382 if (!has_note_cleaned) {
6383 has_note_cleaned = TRUE;
6385 g_free(sbuddy->note);
6386 sbuddy->note = NULL;
6387 sbuddy->is_oof_note = FALSE;
6388 sbuddy->note_since = publish_time;
6390 do_update_status = TRUE;
6392 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6393 /* clean up in case no 'note' element is supplied
6394 * which indicate note removal in client
6396 g_free(sbuddy->note);
6397 sbuddy->note = NULL;
6398 sbuddy->is_oof_note = FALSE;
6399 sbuddy->note_since = publish_time;
6401 xn_node = sipe_xml_child(xn_category, "note/body");
6402 if (xn_node) {
6403 char *tmp;
6404 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6405 g_free(tmp);
6406 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6407 sbuddy->note_since = publish_time;
6409 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6410 uri, sbuddy->note ? sbuddy->note : "");
6412 /* to trigger UI refresh in case no status info is supplied in this update */
6413 do_update_status = TRUE;
6417 /* state */
6418 else if(sipe_strequal(attrVar, "state"))
6420 char *tmp;
6421 int availability;
6422 const sipe_xml *xn_availability;
6423 const sipe_xml *xn_activity;
6424 const sipe_xml *xn_meeting_subject;
6425 const sipe_xml *xn_meeting_location;
6426 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6428 xn_node = sipe_xml_child(xn_category, "state");
6429 if (!xn_node) continue;
6430 xn_availability = sipe_xml_child(xn_node, "availability");
6431 if (!xn_availability) continue;
6432 xn_activity = sipe_xml_child(xn_node, "activity");
6433 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6434 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6436 tmp = sipe_xml_data(xn_availability);
6437 availability = atoi(tmp);
6438 g_free(tmp);
6440 /* activity, meeting_subject, meeting_location */
6441 if (sbuddy) {
6442 char *tmp = NULL;
6444 /* activity */
6445 g_free(sbuddy->activity);
6446 sbuddy->activity = NULL;
6447 if (xn_activity) {
6448 const char *token = sipe_xml_attribute(xn_activity, "token");
6449 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6451 /* from token */
6452 if (!is_empty(token)) {
6453 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6455 /* from custom element */
6456 if (xn_custom) {
6457 char *custom = sipe_xml_data(xn_custom);
6459 if (!is_empty(custom)) {
6460 sbuddy->activity = custom;
6461 custom = NULL;
6463 g_free(custom);
6466 /* meeting_subject */
6467 g_free(sbuddy->meeting_subject);
6468 sbuddy->meeting_subject = NULL;
6469 if (xn_meeting_subject) {
6470 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6472 if (!is_empty(meeting_subject)) {
6473 sbuddy->meeting_subject = meeting_subject;
6474 meeting_subject = NULL;
6476 g_free(meeting_subject);
6478 /* meeting_location */
6479 g_free(sbuddy->meeting_location);
6480 sbuddy->meeting_location = NULL;
6481 if (xn_meeting_location) {
6482 char *meeting_location = sipe_xml_data(xn_meeting_location);
6484 if (!is_empty(meeting_location)) {
6485 sbuddy->meeting_location = meeting_location;
6486 meeting_location = NULL;
6488 g_free(meeting_location);
6491 status = sipe_get_status_by_availability(availability, &tmp);
6492 if (sbuddy->activity && tmp) {
6493 char *tmp2 = sbuddy->activity;
6495 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6496 g_free(tmp);
6497 g_free(tmp2);
6498 } else if (tmp) {
6499 sbuddy->activity = tmp;
6503 do_update_status = TRUE;
6505 /* calendarData */
6506 else if(sipe_strequal(attrVar, "calendarData"))
6508 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
6509 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6510 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6512 if (sbuddy && xn_free_busy) {
6513 if (!has_free_busy_cleaned) {
6514 has_free_busy_cleaned = TRUE;
6516 g_free(sbuddy->cal_start_time);
6517 sbuddy->cal_start_time = NULL;
6519 g_free(sbuddy->cal_free_busy_base64);
6520 sbuddy->cal_free_busy_base64 = NULL;
6522 g_free(sbuddy->cal_free_busy);
6523 sbuddy->cal_free_busy = NULL;
6525 sbuddy->cal_free_busy_published = publish_time;
6528 if (publish_time >= sbuddy->cal_free_busy_published) {
6529 g_free(sbuddy->cal_start_time);
6530 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6532 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6533 15 : 0;
6535 g_free(sbuddy->cal_free_busy_base64);
6536 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6538 g_free(sbuddy->cal_free_busy);
6539 sbuddy->cal_free_busy = NULL;
6541 sbuddy->cal_free_busy_published = publish_time;
6543 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s", sbuddy->cal_start_time, sbuddy->cal_granularity, sbuddy->cal_free_busy_base64);
6547 if (sbuddy && xn_working_hours) {
6548 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6553 if (do_update_status) {
6554 if (!status) { /* no status category in this update, using contact's current status */
6555 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6556 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6557 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6558 status = purple_status_get_id(pstatus);
6561 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6562 sipe_got_user_status(sip, uri, status);
6565 sipe_xml_free(xn_categories);
6568 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6570 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6571 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6572 payload->host = g_strdup(host);
6573 payload->buddies = server;
6574 sipe_subscribe_presence_batched_routed(sip, payload);
6575 sipe_subscribe_presence_batched_routed_free(payload);
6578 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6580 sipe_xml *xn_list;
6581 const sipe_xml *xn_resource;
6582 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6583 g_free, NULL);
6584 GSList *server;
6585 gchar *host;
6587 xn_list = sipe_xml_parse(data, len);
6589 for (xn_resource = sipe_xml_child(xn_list, "resource");
6590 xn_resource;
6591 xn_resource = sipe_xml_twin(xn_resource) )
6593 const char *uri, *state;
6594 const sipe_xml *xn_instance;
6596 xn_instance = sipe_xml_child(xn_resource, "instance");
6597 if (!xn_instance) continue;
6599 uri = sipe_xml_attribute(xn_resource, "uri");
6600 state = sipe_xml_attribute(xn_instance, "state");
6601 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6603 if (strstr(state, "resubscribe")) {
6604 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
6606 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6607 gchar *user = g_strdup(uri);
6608 host = g_strdup(poolFqdn);
6609 server = g_hash_table_lookup(servers, host);
6610 server = g_slist_append(server, user);
6611 g_hash_table_insert(servers, host, server);
6612 } else {
6613 sipe_subscribe_presence_single(sip, (void *) uri);
6618 /* Send out any deferred poolFqdn subscriptions */
6619 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6620 g_hash_table_destroy(servers);
6622 sipe_xml_free(xn_list);
6625 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6627 gchar *uri;
6628 gchar *getbasic;
6629 gchar *activity = NULL;
6630 sipe_xml *pidf;
6631 const sipe_xml *basicstatus = NULL, *tuple, *status;
6632 gboolean isonline = FALSE;
6633 const sipe_xml *display_name_node;
6635 pidf = sipe_xml_parse(data, len);
6636 if (!pidf) {
6637 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6638 return;
6641 if ((tuple = sipe_xml_child(pidf, "tuple")))
6643 if ((status = sipe_xml_child(tuple, "status"))) {
6644 basicstatus = sipe_xml_child(status, "basic");
6648 if (!basicstatus) {
6649 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6650 sipe_xml_free(pidf);
6651 return;
6654 getbasic = sipe_xml_data(basicstatus);
6655 if (!getbasic) {
6656 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6657 sipe_xml_free(pidf);
6658 return;
6661 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6662 if (strstr(getbasic, "open")) {
6663 isonline = TRUE;
6665 g_free(getbasic);
6667 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6669 display_name_node = sipe_xml_child(pidf, "display-name");
6670 if (display_name_node) {
6671 char * display_name = sipe_xml_data(display_name_node);
6673 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6674 g_free(display_name);
6677 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
6678 if ((status = sipe_xml_child(tuple, "status"))) {
6679 if ((basicstatus = sipe_xml_child(status, "activities"))) {
6680 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
6681 activity = sipe_xml_data(basicstatus);
6682 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6688 if (isonline) {
6689 const gchar * status_id = NULL;
6690 if (activity) {
6691 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6692 status_id = SIPE_STATUS_ID_BUSY;
6693 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6694 status_id = SIPE_STATUS_ID_AWAY;
6698 if (!status_id) {
6699 status_id = SIPE_STATUS_ID_AVAILABLE;
6702 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6703 sipe_got_user_status(sip, uri, status_id);
6704 } else {
6705 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6708 g_free(activity);
6709 g_free(uri);
6710 sipe_xml_free(pidf);
6713 /** 2005 */
6714 static void
6715 sipe_user_info_has_updated(struct sipe_account_data *sip,
6716 const sipe_xml *xn_userinfo)
6718 const sipe_xml *xn_states;
6720 g_free(sip->user_states);
6721 sip->user_states = NULL;
6722 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
6723 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
6725 /* this is a hack-around to remove added newline after inner element,
6726 * state in this case, where it shouldn't be.
6727 * After several use of sipe_xml_stringify, amount of added newlines
6728 * grows significantly.
6730 if (orig) {
6731 gchar c, *stripped = orig;
6732 while ((c = *orig++)) {
6733 if ((c != '\n') /* && (c != '\r') */) {
6734 *stripped++ = c;
6737 *stripped = '\0';
6741 /* Publish initial state if not yet.
6742 * Assuming this happens on initial responce to self subscription
6743 * so we've already updated our UserInfo.
6745 if (!sip->initial_state_published) {
6746 send_presence_soap(sip, FALSE);
6747 /* dalayed run */
6748 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY, (Action)sipe_update_calendar, NULL, sip, NULL);
6752 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6754 char *activity = NULL;
6755 const char *epid;
6756 const char *status_id = NULL;
6757 const char *name;
6758 char *uri;
6759 char *self_uri = sip_uri_self(sip);
6760 int avl;
6761 int act;
6762 const char *device_name = NULL;
6763 const char *cal_start_time = NULL;
6764 const char *cal_granularity = NULL;
6765 char *cal_free_busy_base64 = NULL;
6766 struct sipe_buddy *sbuddy;
6767 const sipe_xml *node;
6768 sipe_xml *xn_presentity;
6769 const sipe_xml *xn_availability;
6770 const sipe_xml *xn_activity;
6771 const sipe_xml *xn_display_name;
6772 const sipe_xml *xn_email;
6773 const sipe_xml *xn_phone_number;
6774 const sipe_xml *xn_userinfo;
6775 const sipe_xml *xn_note;
6776 const sipe_xml *xn_oof;
6777 const sipe_xml *xn_state;
6778 const sipe_xml *xn_contact;
6779 char *note;
6780 char *free_activity;
6781 int user_avail;
6782 const char *user_avail_nil;
6783 int res_avail;
6784 time_t user_avail_since = 0;
6785 time_t activity_since = 0;
6787 /* fix for Reuters environment on Linux */
6788 if (data && strstr(data, "encoding=\"utf-16\"")) {
6789 char *tmp_data;
6790 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6791 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
6792 g_free(tmp_data);
6793 } else {
6794 xn_presentity = sipe_xml_parse(data, len);
6797 xn_availability = sipe_xml_child(xn_presentity, "availability");
6798 xn_activity = sipe_xml_child(xn_presentity, "activity");
6799 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
6800 xn_email = sipe_xml_child(xn_presentity, "email");
6801 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
6802 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
6803 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
6804 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
6805 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
6806 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
6807 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
6808 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
6809 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
6810 note = xn_note ? sipe_xml_data(xn_note) : NULL;
6812 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6813 user_avail = 0;
6814 user_avail_since = 0;
6817 free_activity = NULL;
6819 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
6820 uri = sip_uri_from_name(name);
6821 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
6822 epid = sipe_xml_attribute(xn_availability, "epid");
6823 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
6825 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6826 res_avail = sipe_get_availability_by_status(status_id, NULL);
6827 if (user_avail > res_avail) {
6828 res_avail = user_avail;
6829 status_id = sipe_get_status_by_availability(user_avail, NULL);
6832 if (xn_display_name) {
6833 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
6834 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
6835 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
6836 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
6837 char *tel_uri = sip_to_tel_uri(phone_number);
6839 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6840 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6841 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6842 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6844 g_free(tel_uri);
6845 g_free(phone_label);
6846 g_free(phone_number);
6847 g_free(email);
6848 g_free(display_name);
6851 if (xn_contact) {
6852 /* tel */
6853 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
6855 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6856 const char *phone_type = sipe_xml_attribute(node, "type");
6857 char* phone = sipe_xml_data(node);
6859 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6861 g_free(phone);
6865 /* devicePresence */
6866 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
6867 const sipe_xml *xn_device_name;
6868 const sipe_xml *xn_calendar_info;
6869 const sipe_xml *xn_state;
6870 char *state;
6872 /* deviceName */
6873 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
6874 xn_device_name = sipe_xml_child(node, "deviceName");
6875 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
6878 /* calendarInfo */
6879 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
6880 if (xn_calendar_info) {
6881 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
6883 if (cal_start_time) {
6884 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6885 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6887 if (cal_start_time_t_tmp > cal_start_time_t) {
6888 cal_start_time = cal_start_time_tmp;
6889 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6890 g_free(cal_free_busy_base64);
6891 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6893 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
6895 } else {
6896 cal_start_time = cal_start_time_tmp;
6897 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6898 g_free(cal_free_busy_base64);
6899 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6901 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
6905 /* state */
6906 xn_state = sipe_xml_child(node, "states/state");
6907 if (xn_state) {
6908 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
6909 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
6911 state = sipe_xml_data(xn_state);
6912 if (dev_avail_since > user_avail_since &&
6913 dev_avail >= res_avail)
6915 res_avail = dev_avail;
6916 if (!is_empty(state))
6918 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6919 g_free(activity);
6920 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6921 } else if (sipe_strequal(state, "presenting")) {
6922 g_free(activity);
6923 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6924 } else {
6925 activity = state;
6926 state = NULL;
6928 activity_since = dev_avail_since;
6930 status_id = sipe_get_status_by_availability(res_avail, &activity);
6932 g_free(state);
6936 /* oof */
6937 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6938 g_free(activity);
6939 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6940 activity_since = 0;
6943 sbuddy = g_hash_table_lookup(sip->buddies, uri);
6944 if (sbuddy)
6946 g_free(sbuddy->activity);
6947 sbuddy->activity = activity;
6948 activity = NULL;
6950 sbuddy->activity_since = activity_since;
6952 sbuddy->user_avail = user_avail;
6953 sbuddy->user_avail_since = user_avail_since;
6955 g_free(sbuddy->note);
6956 sbuddy->note = NULL;
6957 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6959 sbuddy->is_oof_note = (xn_oof != NULL);
6961 g_free(sbuddy->device_name);
6962 sbuddy->device_name = NULL;
6963 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6965 if (!is_empty(cal_free_busy_base64)) {
6966 g_free(sbuddy->cal_start_time);
6967 sbuddy->cal_start_time = g_strdup(cal_start_time);
6969 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6971 g_free(sbuddy->cal_free_busy_base64);
6972 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6973 cal_free_busy_base64 = NULL;
6975 g_free(sbuddy->cal_free_busy);
6976 sbuddy->cal_free_busy = NULL;
6979 sbuddy->last_non_cal_status_id = status_id;
6980 g_free(sbuddy->last_non_cal_activity);
6981 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6983 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6984 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6986 sip->is_oof_note = sbuddy->is_oof_note;
6988 g_free(sip->note);
6989 sip->note = g_strdup(sbuddy->note);
6991 sip->note_since = time(NULL);
6994 g_free(sip->status);
6995 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6998 g_free(cal_free_busy_base64);
6999 g_free(activity);
7001 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
7002 sipe_got_user_status(sip, uri, status_id);
7004 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
7005 sipe_user_info_has_updated(sip, xn_userinfo);
7008 g_free(note);
7009 sipe_xml_free(xn_presentity);
7010 g_free(uri);
7011 g_free(self_uri);
7014 static void sipe_presence_mime_cb(gpointer user_data,
7015 const gchar *type,
7016 const gchar *body,
7017 gsize length)
7019 if (strstr(type,"application/rlmi+xml")) {
7020 process_incoming_notify_rlmi_resub(user_data, body, length);
7021 } else if (strstr(type, "text/xml+msrtc.pidf")) {
7022 process_incoming_notify_msrtc(user_data, body, length);
7023 } else {
7024 process_incoming_notify_rlmi(user_data, body, length);
7028 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
7030 const char *ctype = sipmsg_find_header(msg, "Content-Type");
7032 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
7034 if (ctype &&
7035 (strstr(ctype, "application/rlmi+xml") ||
7036 strstr(ctype, "application/msrtc-event-categories+xml")))
7038 if (strstr(ctype, "multipart"))
7040 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
7042 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
7044 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
7046 else if(strstr(ctype, "application/rlmi+xml"))
7048 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
7051 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
7053 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
7055 else
7057 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
7061 static void sipe_presence_timeout_mime_cb(gpointer user_data,
7062 SIPE_UNUSED_PARAMETER const gchar *type,
7063 const gchar *body,
7064 gsize length)
7066 GSList **buddies = user_data;
7067 sipe_xml *xml = sipe_xml_parse(body, length);
7069 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
7070 const gchar *uri = sipe_xml_attribute(xml, "uri");
7071 const sipe_xml *xn_category;
7074 * automaton: presence is never expected to change
7076 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
7078 for (xn_category = sipe_xml_child(xml, "category");
7079 xn_category;
7080 xn_category = sipe_xml_twin(xn_category)) {
7081 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
7082 "contactCard")) {
7083 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
7084 if (node) {
7085 char *boolean = sipe_xml_data(node);
7086 if (sipe_strequal(boolean, "true")) {
7087 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
7088 uri);
7089 uri = NULL;
7091 g_free(boolean);
7093 break;
7097 if (uri) {
7098 *buddies = g_slist_append(*buddies, sip_uri(uri));
7102 sipe_xml_free(xml);
7105 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
7107 const char *ctype = sipmsg_find_header(msg, "Content-Type");
7108 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7110 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
7112 if (ctype &&
7113 strstr(ctype, "multipart") &&
7114 (strstr(ctype, "application/rlmi+xml") ||
7115 strstr(ctype, "application/msrtc-event-categories+xml"))) {
7116 GSList *buddies = NULL;
7118 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
7120 if (buddies) {
7121 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
7122 payload->host = g_strdup(who);
7123 payload->buddies = buddies;
7124 sipe_schedule_action(action_name, timeout,
7125 sipe_subscribe_presence_batched_routed,
7126 sipe_subscribe_presence_batched_routed_free,
7127 sip, payload);
7128 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
7131 } else {
7132 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7133 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
7135 g_free(action_name);
7139 * Dispatcher for all incoming subscription information
7140 * whether it comes from NOTIFY, BENOTIFY requests or
7141 * piggy-backed to subscription's OK responce.
7143 * @param request whether initiated from BE/NOTIFY request or OK-response message.
7144 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
7146 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
7148 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
7149 const gchar *event = sipmsg_find_header(msg, "Event");
7150 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
7151 char *tmp;
7153 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
7154 event ? event : "",
7155 tmp = fix_newlines(msg->body));
7156 g_free(tmp);
7157 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
7159 /* implicit subscriptions */
7160 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
7161 sipe_process_imdn(sip, msg);
7164 if (event) {
7165 /* for one off subscriptions (send with Expire: 0) */
7166 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
7168 sipe_process_provisioning_v2(sip, msg);
7170 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
7172 sipe_process_provisioning(sip, msg);
7174 else if (sipe_strcase_equal(event, "presence"))
7176 sipe_process_presence(sip, msg);
7178 else if (sipe_strcase_equal(event, "registration-notify"))
7180 sipe_process_registration_notify(sip, msg);
7183 if (!subscription_state || strstr(subscription_state, "active"))
7185 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
7187 sipe_process_roaming_contacts(sip, msg);
7189 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
7191 sipe_process_roaming_self(sip, msg);
7193 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
7195 sipe_process_roaming_acl(sip, msg);
7197 else if (sipe_strcase_equal(event, "presence.wpending"))
7199 sipe_process_presence_wpending(sip, msg);
7201 else if (sipe_strcase_equal(event, "conference"))
7203 sipe_process_conference(sip, msg);
7208 /* The server sends status 'terminated' */
7209 if (subscription_state && strstr(subscription_state, "terminated") ) {
7210 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
7211 gchar *key = sipe_get_subscription_key(event, who);
7213 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
7214 g_free(who);
7216 if (g_hash_table_lookup(sip->subscriptions, key)) {
7217 g_hash_table_remove(sip->subscriptions, key);
7218 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
7221 g_free(key);
7224 if (!request && event) {
7225 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
7226 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
7227 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
7229 if (timeout) {
7230 /* 2 min ahead of expiration */
7231 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
7233 if (sipe_strcase_equal(event, "presence.wpending") &&
7234 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
7236 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
7237 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
7238 g_free(action_name);
7240 else if (sipe_strcase_equal(event, "presence") &&
7241 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
7243 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
7244 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
7246 if (sip->batched_support) {
7247 sipe_process_presence_timeout(sip, msg, who, timeout);
7249 else {
7250 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
7251 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
7253 g_free(action_name);
7254 g_free(who);
7259 /* The client responses on received a NOTIFY message */
7260 if (request && !benotify)
7262 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7267 * Whether user manually changed status or
7268 * it was changed automatically due to user
7269 * became inactive/active again
7271 static gboolean
7272 sipe_is_user_state(struct sipe_account_data *sip)
7274 gboolean res;
7275 time_t now = time(NULL);
7277 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
7278 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
7280 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
7282 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
7283 return res;
7286 static void
7287 send_presence_soap0(struct sipe_account_data *sip,
7288 gboolean do_publish_calendar,
7289 gboolean do_reset_status)
7291 struct sipe_ews* ews = sip->ews;
7292 int availability = 0;
7293 int activity = 0;
7294 gchar *body;
7295 gchar *tmp;
7296 gchar *tmp2 = NULL;
7297 gchar *res_note = NULL;
7298 gchar *res_oof = NULL;
7299 const gchar *note_pub = NULL;
7300 gchar *states = NULL;
7301 gchar *calendar_data = NULL;
7302 gchar *epid = get_epid(sip);
7303 time_t now = time(NULL);
7304 gchar *since_time_str = sipe_utils_time_to_str(now);
7305 const gchar *oof_note = ews ? sipe_ews_get_oof_note(ews) : NULL;
7306 const char *user_input;
7307 gboolean pub_oof = ews && oof_note && (!sip->note || ews->updated > sip->note_since);
7309 if (oof_note && sip->note) {
7310 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews->oof_start))));
7311 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
7314 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
7316 if (!sip->initial_state_published ||
7317 do_reset_status)
7319 g_free(sip->status);
7320 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
7323 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
7325 /* Note */
7326 if (pub_oof) {
7327 note_pub = oof_note;
7328 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
7329 ews->published = TRUE;
7330 } else if (sip->note) {
7331 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in ews already */
7332 g_free(sip->note);
7333 sip->note = NULL;
7334 sip->is_oof_note = FALSE;
7335 sip->note_since = 0;
7336 } else {
7337 note_pub = sip->note;
7338 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
7342 if (note_pub)
7344 /* to protocol internal plain text format */
7345 tmp = sipe_backend_markup_strip_html(note_pub);
7346 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
7347 g_free(tmp);
7350 /* User State */
7351 if (!do_reset_status) {
7352 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7354 gchar *activity_token = NULL;
7355 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7357 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7358 avail_2007,
7359 since_time_str,
7360 epid,
7361 activity_token);
7362 g_free(activity_token);
7364 else /* preserve existing publication */
7366 if (sip->user_states) {
7367 states = g_strdup(sip->user_states);
7370 } else {
7371 /* do nothing - then User state will be erased */
7373 sip->initial_state_published = TRUE;
7375 /* CalendarInfo */
7376 if (ews && (!is_empty(ews->legacy_dn) || !is_empty(ews->email)) && ews->fb_start && !is_empty(ews->free_busy))
7378 char *fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7379 char *free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7380 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7381 !is_empty(ews->legacy_dn) ? ews->legacy_dn : ews->email,
7382 fb_start_str,
7383 free_busy_base64);
7384 g_free(fb_start_str);
7385 g_free(free_busy_base64);
7388 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7390 /* forming resulting XML */
7391 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7392 sip->username,
7393 availability,
7394 activity,
7395 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7396 res_note ? res_note : "",
7397 res_oof ? res_oof : "",
7398 states ? states : "",
7399 calendar_data ? calendar_data : "",
7400 epid,
7401 since_time_str,
7402 since_time_str,
7403 user_input);
7404 g_free(tmp);
7405 g_free(tmp2);
7406 g_free(res_note);
7407 g_free(states);
7408 g_free(calendar_data);
7410 send_soap_request(sip, body);
7412 g_free(body);
7413 g_free(since_time_str);
7414 g_free(epid);
7417 void
7418 send_presence_soap(struct sipe_account_data *sip,
7419 gboolean do_publish_calendar)
7421 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7425 static gboolean
7426 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7427 struct sipmsg *msg,
7428 struct transaction *trans)
7430 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7432 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7433 sipe_xml *xml;
7434 const sipe_xml *node;
7435 gchar *fault_code;
7436 GHashTable *faults;
7437 int index_our;
7438 gboolean has_device_publication = FALSE;
7440 xml = sipe_xml_parse(msg->body, msg->bodylen);
7442 /* test if version mismatch fault */
7443 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
7444 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7445 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7446 g_free(fault_code);
7447 sipe_xml_free(xml);
7448 return TRUE;
7450 g_free(fault_code);
7452 /* accumulating information about faulty versions */
7453 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7454 for (node = sipe_xml_child(xml, "details/operation");
7455 node;
7456 node = sipe_xml_twin(node))
7458 const gchar *index = sipe_xml_attribute(node, "index");
7459 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
7461 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7462 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7464 sipe_xml_free(xml);
7466 /* here we are parsing own request to figure out what publication
7467 * referensed here only by index went wrong
7469 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
7471 /* publication */
7472 for (node = sipe_xml_child(xml, "publications/publication"),
7473 index_our = 1; /* starts with 1 - our first publication */
7474 node;
7475 node = sipe_xml_twin(node), index_our++)
7477 gchar *idx = g_strdup_printf("%d", index_our);
7478 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7479 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
7480 g_free(idx);
7482 if (sipe_strequal("device", categoryName)) {
7483 has_device_publication = TRUE;
7486 if (curVersion) { /* fault exist on this index */
7487 const gchar *container = sipe_xml_attribute(node, "container");
7488 const gchar *instance = sipe_xml_attribute(node, "instance");
7489 /* key is <category><instance><container> */
7490 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7491 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7493 if (category) {
7494 struct sipe_publication *publication =
7495 g_hash_table_lookup(category, key);
7497 SIPE_DEBUG_INFO("key is %s", key);
7499 if (publication) {
7500 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7501 key, curVersion, publication->version);
7502 /* updating publication's version to the correct one */
7503 publication->version = atoi(curVersion);
7505 } else {
7506 /* We somehow lost this category from our publications... */
7507 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7508 publication->category = g_strdup(categoryName);
7509 publication->instance = atoi(instance);
7510 publication->container = atoi(container);
7511 publication->version = atoi(curVersion);
7512 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7513 g_free, (GDestroyNotify)free_publication);
7514 g_hash_table_insert(category, g_strdup(key), publication);
7515 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7516 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7518 g_free(key);
7521 sipe_xml_free(xml);
7522 g_hash_table_destroy(faults);
7524 /* rebublishing with right versions */
7525 if (has_device_publication) {
7526 send_publish_category_initial(sip);
7527 } else {
7528 send_presence_status(sip);
7531 return TRUE;
7535 * Returns 'device' XML part for publication.
7536 * Must be g_free'd after use.
7538 static gchar *
7539 sipe_publish_get_category_device(struct sipe_account_data *sip)
7541 gchar *uri;
7542 gchar *doc;
7543 gchar *epid = get_epid(sip);
7544 gchar *uuid = generateUUIDfromEPID(epid);
7545 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7546 /* key is <category><instance><container> */
7547 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7548 struct sipe_publication *publication =
7549 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7551 g_free(key);
7552 g_free(epid);
7554 uri = sip_uri_self(sip);
7555 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7556 device_instance,
7557 publication ? publication->version : 0,
7558 uuid,
7559 uri,
7560 "00:00:00+01:00", /* @TODO make timezone real*/
7561 g_get_host_name()
7564 g_free(uri);
7565 g_free(uuid);
7567 return doc;
7571 * A service method - use
7572 * - send_publish_get_category_state_machine and
7573 * - send_publish_get_category_state_user instead.
7574 * Must be g_free'd after use.
7576 static gchar *
7577 sipe_publish_get_category_state(struct sipe_account_data *sip,
7578 gboolean is_user_state)
7580 int availability = sipe_get_availability_by_status(sip->status, NULL);
7581 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7582 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7583 /* key is <category><instance><container> */
7584 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7585 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7586 struct sipe_publication *publication_2 =
7587 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7588 struct sipe_publication *publication_3 =
7589 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7591 g_free(key_2);
7592 g_free(key_3);
7594 if (publication_2 && (publication_2->availability == availability))
7596 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7597 return NULL; /* nothing to update */
7600 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7601 instance,
7602 publication_2 ? publication_2->version : 0,
7603 availability,
7604 instance,
7605 publication_3 ? publication_3->version : 0,
7606 availability);
7610 * Only Busy and OOF calendar event are published.
7611 * Different instances are used for that.
7613 * Must be g_free'd after use.
7615 static gchar *
7616 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7617 struct sipe_cal_event *event,
7618 const char *uri,
7619 int cal_satus)
7621 gchar *start_time_str;
7622 int availability = 0;
7623 gchar *res;
7624 gchar *tmp = NULL;
7625 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7626 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7627 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7629 /* key is <category><instance><container> */
7630 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7631 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7632 struct sipe_publication *publication_2 =
7633 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7634 struct sipe_publication *publication_3 =
7635 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7637 g_free(key_2);
7638 g_free(key_3);
7640 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7641 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7642 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7643 return NULL;
7646 if (event &&
7647 publication_3 &&
7648 (publication_3->availability == availability) &&
7649 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7651 g_free(tmp);
7652 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7653 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7654 return NULL; /* nothing to update */
7656 g_free(tmp);
7658 if (event &&
7659 (event->cal_status == SIPE_CAL_BUSY ||
7660 event->cal_status == SIPE_CAL_OOF))
7662 gchar *availability_xml_str = NULL;
7663 gchar *activity_xml_str = NULL;
7665 if (event->cal_status == SIPE_CAL_BUSY) {
7666 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7669 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7670 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7671 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7672 "minAvailability=\"6500\"",
7673 "maxAvailability=\"8999\"");
7674 } else if (event->cal_status == SIPE_CAL_OOF) {
7675 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7676 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7677 "minAvailability=\"12000\"",
7678 "");
7680 start_time_str = sipe_utils_time_to_str(event->start_time);
7682 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7683 instance,
7684 publication_2 ? publication_2->version : 0,
7685 uri,
7686 start_time_str,
7687 availability_xml_str ? availability_xml_str : "",
7688 activity_xml_str ? activity_xml_str : "",
7689 event->subject ? event->subject : "",
7690 event->location ? event->location : "",
7692 instance,
7693 publication_3 ? publication_3->version : 0,
7694 uri,
7695 start_time_str,
7696 availability_xml_str ? availability_xml_str : "",
7697 activity_xml_str ? activity_xml_str : "",
7698 event->subject ? event->subject : "",
7699 event->location ? event->location : ""
7701 g_free(start_time_str);
7702 g_free(availability_xml_str);
7703 g_free(activity_xml_str);
7706 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7708 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7709 instance,
7710 publication_2 ? publication_2->version : 0,
7712 instance,
7713 publication_3 ? publication_3->version : 0
7717 return res;
7721 * Returns 'machineState' XML part for publication.
7722 * Must be g_free'd after use.
7724 static gchar *
7725 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7727 return sipe_publish_get_category_state(sip, FALSE);
7731 * Returns 'userState' XML part for publication.
7732 * Must be g_free'd after use.
7734 static gchar *
7735 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7737 return sipe_publish_get_category_state(sip, TRUE);
7741 * Returns 'note' XML part for publication.
7742 * Must be g_free'd after use.
7744 * Protocol format for Note is plain text.
7746 * @param note a note in Sipe internal HTML format
7747 * @param note_type either personal or OOF
7749 static gchar *
7750 sipe_publish_get_category_note(struct sipe_account_data *sip,
7751 const char *note, /* html */
7752 const char *note_type,
7753 time_t note_start,
7754 time_t note_end)
7756 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7757 /* key is <category><instance><container> */
7758 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7759 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7760 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7762 struct sipe_publication *publication_note_200 =
7763 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7764 struct sipe_publication *publication_note_300 =
7765 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7766 struct sipe_publication *publication_note_400 =
7767 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7769 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7770 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7771 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7772 char *res, *tmp1, *tmp2, *tmp3;
7773 char *start_time_attr;
7774 char *end_time_attr;
7776 g_free(tmp);
7777 tmp = NULL;
7778 g_free(key_note_200);
7779 g_free(key_note_300);
7780 g_free(key_note_400);
7782 /* we even need to republish empty note */
7783 if (sipe_strequal(n1, n2))
7785 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7786 g_free(n1);
7787 return NULL; /* nothing to update */
7790 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7791 g_free(tmp);
7792 tmp = NULL;
7793 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7794 g_free(tmp);
7796 if (n1) {
7797 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7798 instance,
7799 200,
7800 publication_note_200 ? publication_note_200->version : 0,
7801 note_type,
7802 start_time_attr ? start_time_attr : "",
7803 end_time_attr ? end_time_attr : "",
7804 n1);
7806 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7807 instance,
7808 300,
7809 publication_note_300 ? publication_note_300->version : 0,
7810 note_type,
7811 start_time_attr ? start_time_attr : "",
7812 end_time_attr ? end_time_attr : "",
7813 n1);
7815 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7816 instance,
7817 400,
7818 publication_note_400 ? publication_note_400->version : 0,
7819 note_type,
7820 start_time_attr ? start_time_attr : "",
7821 end_time_attr ? end_time_attr : "",
7822 n1);
7823 } else {
7824 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7825 "note",
7826 instance,
7827 200,
7828 publication_note_200 ? publication_note_200->version : 0,
7829 "static");
7830 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7831 "note",
7832 instance,
7833 300,
7834 publication_note_200 ? publication_note_200->version : 0,
7835 "static");
7836 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7837 "note",
7838 instance,
7839 400,
7840 publication_note_200 ? publication_note_200->version : 0,
7841 "static");
7843 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7845 g_free(start_time_attr);
7846 g_free(end_time_attr);
7847 g_free(tmp1);
7848 g_free(tmp2);
7849 g_free(tmp3);
7850 g_free(n1);
7852 return res;
7856 * Returns 'calendarData' XML part with WorkingHours for publication.
7857 * Must be g_free'd after use.
7859 static gchar *
7860 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7862 struct sipe_ews* ews = sip->ews;
7864 /* key is <category><instance><container> */
7865 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7866 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7867 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7868 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7869 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7870 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7872 struct sipe_publication *publication_cal_1 =
7873 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7874 struct sipe_publication *publication_cal_100 =
7875 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7876 struct sipe_publication *publication_cal_200 =
7877 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7878 struct sipe_publication *publication_cal_300 =
7879 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7880 struct sipe_publication *publication_cal_400 =
7881 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7882 struct sipe_publication *publication_cal_32000 =
7883 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7885 const char *n1 = ews ? ews->working_hours_xml_str : NULL;
7886 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7888 g_free(key_cal_1);
7889 g_free(key_cal_100);
7890 g_free(key_cal_200);
7891 g_free(key_cal_300);
7892 g_free(key_cal_400);
7893 g_free(key_cal_32000);
7895 if (!ews || is_empty(ews->email) || is_empty(ews->working_hours_xml_str)) {
7896 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7897 return NULL;
7900 if (sipe_strequal(n1, n2))
7902 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7903 return NULL; /* nothing to update */
7906 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7907 /* 1 */
7908 publication_cal_1 ? publication_cal_1->version : 0,
7909 ews->email,
7910 ews->working_hours_xml_str,
7911 /* 100 - Public */
7912 publication_cal_100 ? publication_cal_100->version : 0,
7913 /* 200 - Company */
7914 publication_cal_200 ? publication_cal_200->version : 0,
7915 ews->email,
7916 ews->working_hours_xml_str,
7917 /* 300 - Team */
7918 publication_cal_300 ? publication_cal_300->version : 0,
7919 ews->email,
7920 ews->working_hours_xml_str,
7921 /* 400 - Personal */
7922 publication_cal_400 ? publication_cal_400->version : 0,
7923 ews->email,
7924 ews->working_hours_xml_str,
7925 /* 32000 - Blocked */
7926 publication_cal_32000 ? publication_cal_32000->version : 0
7931 * Returns 'calendarData' XML part with FreeBusy for publication.
7932 * Must be g_free'd after use.
7934 static gchar *
7935 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7937 struct sipe_ews* ews = sip->ews;
7938 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7939 char *fb_start_str;
7940 char *free_busy_base64;
7941 const char *st;
7942 const char *fb;
7943 char *res;
7945 /* key is <category><instance><container> */
7946 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7947 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7948 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7949 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7950 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7951 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7953 struct sipe_publication *publication_cal_1 =
7954 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7955 struct sipe_publication *publication_cal_100 =
7956 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7957 struct sipe_publication *publication_cal_200 =
7958 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7959 struct sipe_publication *publication_cal_300 =
7960 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7961 struct sipe_publication *publication_cal_400 =
7962 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7963 struct sipe_publication *publication_cal_32000 =
7964 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7966 g_free(key_cal_1);
7967 g_free(key_cal_100);
7968 g_free(key_cal_200);
7969 g_free(key_cal_300);
7970 g_free(key_cal_400);
7971 g_free(key_cal_32000);
7973 if (!ews || is_empty(ews->email) || !ews->fb_start || is_empty(ews->free_busy)) {
7974 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7975 return NULL;
7978 fb_start_str = sipe_utils_time_to_str(ews->fb_start);
7979 free_busy_base64 = sipe_cal_get_freebusy_base64(ews->free_busy);
7981 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7982 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7984 /* we will rebuplish the same data to refresh publication time,
7985 * so if data from multiple sources, most recent will be choosen
7987 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7989 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7990 // g_free(fb_start_str);
7991 // g_free(free_busy_base64);
7992 // return NULL; /* nothing to update */
7995 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7996 /* 1 */
7997 cal_data_instance,
7998 publication_cal_1 ? publication_cal_1->version : 0,
7999 /* 100 - Public */
8000 cal_data_instance,
8001 publication_cal_100 ? publication_cal_100->version : 0,
8002 /* 200 - Company */
8003 cal_data_instance,
8004 publication_cal_200 ? publication_cal_200->version : 0,
8005 ews->email,
8006 fb_start_str,
8007 free_busy_base64,
8008 /* 300 - Team */
8009 cal_data_instance,
8010 publication_cal_300 ? publication_cal_300->version : 0,
8011 ews->email,
8012 fb_start_str,
8013 free_busy_base64,
8014 /* 400 - Personal */
8015 cal_data_instance,
8016 publication_cal_400 ? publication_cal_400->version : 0,
8017 ews->email,
8018 fb_start_str,
8019 free_busy_base64,
8020 /* 32000 - Blocked */
8021 cal_data_instance,
8022 publication_cal_32000 ? publication_cal_32000->version : 0
8025 g_free(fb_start_str);
8026 g_free(free_busy_base64);
8027 return res;
8030 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
8032 gchar *uri;
8033 gchar *doc;
8034 gchar *tmp;
8035 gchar *hdr;
8037 uri = sip_uri_self(sip);
8038 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
8039 uri,
8040 publications);
8042 tmp = get_contact(sip);
8043 hdr = g_strdup_printf("Contact: %s\r\n"
8044 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
8046 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
8048 g_free(tmp);
8049 g_free(hdr);
8050 g_free(uri);
8051 g_free(doc);
8054 static void
8055 send_publish_category_initial(struct sipe_account_data *sip)
8057 gchar *pub_device = sipe_publish_get_category_device(sip);
8058 gchar *pub_machine;
8059 gchar *publications;
8061 g_free(sip->status);
8062 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
8064 pub_machine = sipe_publish_get_category_state_machine(sip);
8065 publications = g_strdup_printf("%s%s",
8066 pub_device,
8067 pub_machine ? pub_machine : "");
8068 g_free(pub_device);
8069 g_free(pub_machine);
8071 send_presence_publish(sip, publications);
8072 g_free(publications);
8075 static void
8076 send_presence_category_publish(struct sipe_account_data *sip)
8078 gchar *pub_state = sipe_is_user_state(sip) ?
8079 sipe_publish_get_category_state_user(sip) :
8080 sipe_publish_get_category_state_machine(sip);
8081 gchar *pub_note = sipe_publish_get_category_note(sip,
8082 sip->note,
8083 sip->is_oof_note ? "OOF" : "personal",
8086 gchar *publications;
8088 if (!pub_state && !pub_note) {
8089 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
8090 return;
8093 publications = g_strdup_printf("%s%s",
8094 pub_state ? pub_state : "",
8095 pub_note ? pub_note : "");
8097 g_free(pub_state);
8098 g_free(pub_note);
8100 send_presence_publish(sip, publications);
8101 g_free(publications);
8105 * Publishes self status
8106 * based on own calendar information.
8108 * For 2007+
8110 void
8111 publish_calendar_status_self(struct sipe_account_data *sip)
8113 struct sipe_cal_event* event = NULL;
8114 gchar *pub_cal_working_hours = NULL;
8115 gchar *pub_cal_free_busy = NULL;
8116 gchar *pub_calendar = NULL;
8117 gchar *pub_calendar2 = NULL;
8118 gchar *pub_oof_note = NULL;
8119 const gchar *oof_note;
8120 time_t oof_start = 0;
8121 time_t oof_end = 0;
8123 if (!sip->ews) {
8124 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
8125 return;
8128 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
8129 if (sip->ews->cal_events) {
8130 event = sipe_cal_get_event(sip->ews->cal_events, time(NULL));
8133 if (!event) {
8134 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
8135 } else {
8136 char *desc = sipe_cal_event_describe(event);
8137 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
8138 g_free(desc);
8141 /* Logic
8142 if OOF
8143 OOF publish, Busy clean
8144 ilse if Busy
8145 OOF clean, Busy publish
8146 else
8147 OOF clean, Busy clean
8149 if (event && event->cal_status == SIPE_CAL_OOF) {
8150 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_OOF);
8151 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8152 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
8153 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8154 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->ews->email, SIPE_CAL_BUSY);
8155 } else {
8156 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_OOF);
8157 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->ews->email, SIPE_CAL_BUSY);
8160 oof_note = sipe_ews_get_oof_note(sip->ews);
8161 if (sipe_strequal("Scheduled", sip->ews->oof_state)) {
8162 oof_start = sip->ews->oof_start;
8163 oof_end = sip->ews->oof_end;
8165 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
8167 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
8168 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
8170 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
8171 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
8172 } else {
8173 gchar *publications = g_strdup_printf("%s%s%s%s%s",
8174 pub_cal_working_hours ? pub_cal_working_hours : "",
8175 pub_cal_free_busy ? pub_cal_free_busy : "",
8176 pub_calendar ? pub_calendar : "",
8177 pub_calendar2 ? pub_calendar2 : "",
8178 pub_oof_note ? pub_oof_note : "");
8180 send_presence_publish(sip, publications);
8181 g_free(publications);
8184 g_free(pub_cal_working_hours);
8185 g_free(pub_cal_free_busy);
8186 g_free(pub_calendar);
8187 g_free(pub_calendar2);
8188 g_free(pub_oof_note);
8190 /* repeat scheduling */
8191 sipe_sched_calendar_status_self_publish(sip, time(NULL));
8194 static void send_presence_status(struct sipe_account_data *sip)
8196 PurpleStatus * status = purple_account_get_active_status(sip->account);
8198 if (!status) return;
8200 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
8201 purple_status_get_id(status) ? purple_status_get_id(status) : "",
8202 sipe_is_user_state(sip) ? "USER" : "MACHINE");
8204 if (sip->ocs2007) {
8205 send_presence_category_publish(sip);
8206 } else {
8207 send_presence_soap(sip, FALSE);
8211 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
8213 gboolean found = FALSE;
8214 const char *method = msg->method ? msg->method : "NOT FOUND";
8215 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
8216 if (msg->response == 0) { /* request */
8217 if (sipe_strequal(method, "MESSAGE")) {
8218 process_incoming_message(sip, msg);
8219 found = TRUE;
8220 } else if (sipe_strequal(method, "NOTIFY")) {
8221 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
8222 process_incoming_notify(sip, msg, TRUE, FALSE);
8223 found = TRUE;
8224 } else if (sipe_strequal(method, "BENOTIFY")) {
8225 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
8226 process_incoming_notify(sip, msg, TRUE, TRUE);
8227 found = TRUE;
8228 } else if (sipe_strequal(method, "INVITE")) {
8229 process_incoming_invite(sip, msg);
8230 found = TRUE;
8231 } else if (sipe_strequal(method, "REFER")) {
8232 process_incoming_refer(sip, msg);
8233 found = TRUE;
8234 } else if (sipe_strequal(method, "OPTIONS")) {
8235 process_incoming_options(sip, msg);
8236 found = TRUE;
8237 } else if (sipe_strequal(method, "INFO")) {
8238 process_incoming_info(sip, msg);
8239 found = TRUE;
8240 } else if (sipe_strequal(method, "ACK")) {
8241 // ACK's don't need any response
8242 found = TRUE;
8243 } else if (sipe_strequal(method, "SUBSCRIBE")) {
8244 // LCS 2005 sends us these - just respond 200 OK
8245 found = TRUE;
8246 send_sip_response(sip->gc, msg, 200, "OK", NULL);
8247 } else if (sipe_strequal(method, "BYE")) {
8248 process_incoming_bye(sip, msg);
8249 found = TRUE;
8250 } else {
8251 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
8253 } else { /* response */
8254 struct transaction *trans = transactions_find(sip, msg);
8255 if (trans) {
8256 if (msg->response == 407) {
8257 gchar *resend, *auth;
8258 const gchar *ptmp;
8260 if (sip->proxy.retries > 30) return;
8261 sip->proxy.retries++;
8262 /* do proxy authentication */
8264 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
8266 fill_auth(ptmp, &sip->proxy);
8267 auth = auth_header(sip, &sip->proxy, trans->msg);
8268 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
8269 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
8270 g_free(auth);
8271 resend = sipmsg_to_string(trans->msg);
8272 /* resend request */
8273 sendout_pkt(sip->gc, resend);
8274 g_free(resend);
8275 } else {
8276 if (msg->response < 200) {
8277 /* ignore provisional response */
8278 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
8279 } else {
8280 sip->proxy.retries = 0;
8281 if (sipe_strequal(trans->msg->method, "REGISTER")) {
8282 if (msg->response == 401)
8284 sip->registrar.retries++;
8286 else
8288 sip->registrar.retries = 0;
8290 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
8291 } else {
8292 if (msg->response == 401) {
8293 gchar *resend, *auth, *ptmp;
8294 const char* auth_scheme;
8296 if (sip->registrar.retries > 4) return;
8297 sip->registrar.retries++;
8299 auth_scheme = sipe_get_auth_scheme_name(sip);
8300 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
8302 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
8303 if (!ptmp) {
8304 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
8305 sip->gc->wants_to_die = TRUE;
8306 purple_connection_error(sip->gc, tmp2);
8307 g_free(tmp2);
8308 return;
8311 fill_auth(ptmp, &sip->registrar);
8312 auth = auth_header(sip, &sip->registrar, trans->msg);
8313 sipmsg_remove_header_now(trans->msg, "Authorization");
8314 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
8315 g_free(auth);
8316 resend = sipmsg_to_string(trans->msg);
8317 /* resend request */
8318 sendout_pkt(sip->gc, resend);
8319 g_free(resend);
8323 if (trans->callback) {
8324 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8325 /* call the callback to process response*/
8326 (trans->callback)(sip, msg, trans);
8329 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
8330 transactions_remove(sip, trans);
8334 found = TRUE;
8335 } else {
8336 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8339 if (!found) {
8340 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
8344 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
8346 char *cur;
8347 char *dummy;
8348 char *tmp;
8349 struct sipmsg *msg;
8350 int restlen;
8351 cur = conn->inbuf;
8353 /* according to the RFC remove CRLF at the beginning */
8354 while (*cur == '\r' || *cur == '\n') {
8355 cur++;
8357 if (cur != conn->inbuf) {
8358 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
8359 conn->inbufused = strlen(conn->inbuf);
8362 /* Received a full Header? */
8363 sip->processing_input = TRUE;
8364 while (sip->processing_input &&
8365 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
8366 time_t currtime = time(NULL);
8367 cur += 2;
8368 cur[0] = '\0';
8369 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
8370 g_free(tmp);
8371 msg = sipmsg_parse_header(conn->inbuf);
8372 cur[0] = '\r';
8373 cur += 2;
8374 restlen = conn->inbufused - (cur - conn->inbuf);
8375 if (msg && restlen >= msg->bodylen) {
8376 dummy = g_malloc(msg->bodylen + 1);
8377 memcpy(dummy, cur, msg->bodylen);
8378 dummy[msg->bodylen] = '\0';
8379 msg->body = dummy;
8380 cur += msg->bodylen;
8381 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
8382 conn->inbufused = strlen(conn->inbuf);
8383 } else {
8384 if (msg){
8385 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
8386 sipmsg_free(msg);
8388 return;
8391 /*if (msg->body) {
8392 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8395 // Verify the signature before processing it
8396 if (sip->registrar.gssapi_context) {
8397 struct sipmsg_breakdown msgbd;
8398 gchar *signature_input_str;
8399 gchar *rspauth;
8400 msgbd.msg = msg;
8401 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8402 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8404 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8406 if (rspauth != NULL) {
8407 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8408 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8409 process_input_message(sip, msg);
8410 } else {
8411 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8412 purple_connection_error(sip->gc, _("Invalid message signature received"));
8413 sip->gc->wants_to_die = TRUE;
8415 } else if (msg->response == 401) {
8416 purple_connection_error(sip->gc, _("Authentication failed"));
8417 sip->gc->wants_to_die = TRUE;
8419 g_free(signature_input_str);
8421 g_free(rspauth);
8422 sipmsg_breakdown_free(&msgbd);
8423 } else {
8424 process_input_message(sip, msg);
8427 sipmsg_free(msg);
8431 static void sipe_udp_process(gpointer data, gint source,
8432 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
8434 PurpleConnection *gc = data;
8435 struct sipe_account_data *sip = gc->proto_data;
8436 int len;
8438 static char buffer[65536];
8439 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
8440 time_t currtime = time(NULL);
8441 struct sipmsg *msg;
8442 buffer[len] = '\0';
8443 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), buffer);
8444 msg = sipmsg_parse_msg(buffer);
8445 if (msg) process_input_message(sip, msg);
8449 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
8451 struct sipe_account_data *sip = gc->proto_data;
8452 PurpleSslConnection *gsc = sip->gsc;
8454 SIPE_DEBUG_ERROR("%s", debug);
8455 purple_connection_error(gc, msg);
8457 /* Invalidate this connection. Next send will open a new one */
8458 if (gsc) {
8459 connection_remove(sip, gsc->fd);
8460 purple_ssl_close(gsc);
8462 sip->gsc = NULL;
8463 sip->fd = -1;
8466 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8467 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8469 PurpleConnection *gc = data;
8470 struct sipe_account_data *sip;
8471 struct sip_connection *conn;
8472 int readlen, len;
8473 gboolean firstread = TRUE;
8475 /* NOTE: This check *IS* necessary */
8476 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
8477 purple_ssl_close(gsc);
8478 return;
8481 sip = gc->proto_data;
8482 conn = connection_find(sip, gsc->fd);
8483 if (conn == NULL) {
8484 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8485 gc->wants_to_die = TRUE;
8486 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
8487 return;
8490 /* Read all available data from the SSL connection */
8491 do {
8492 /* Increase input buffer size as needed */
8493 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8494 conn->inbuflen += SIMPLE_BUF_INC;
8495 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8496 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn->inbuflen);
8499 /* Try to read as much as there is space left in the buffer */
8500 readlen = conn->inbuflen - conn->inbufused - 1;
8501 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
8503 if (len < 0 && errno == EAGAIN) {
8504 /* Try again later */
8505 return;
8506 } else if (len < 0) {
8507 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
8508 return;
8509 } else if (firstread && (len == 0)) {
8510 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
8511 return;
8514 conn->inbufused += len;
8515 firstread = FALSE;
8517 /* Equivalence indicates that there is possibly more data to read */
8518 } while (len == readlen);
8520 conn->inbuf[conn->inbufused] = '\0';
8521 process_input(sip, conn);
8525 static void sipe_input_cb(gpointer data, gint source,
8526 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8528 PurpleConnection *gc = data;
8529 struct sipe_account_data *sip = gc->proto_data;
8530 int len;
8531 struct sip_connection *conn = connection_find(sip, source);
8532 if (!conn) {
8533 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8534 return;
8537 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
8538 conn->inbuflen += SIMPLE_BUF_INC;
8539 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
8542 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
8544 if (len < 0 && errno == EAGAIN)
8545 return;
8546 else if (len <= 0) {
8547 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8548 connection_remove(sip, source);
8549 if (sip->fd == source) sip->fd = -1;
8550 return;
8553 conn->inbufused += len;
8554 conn->inbuf[conn->inbufused] = '\0';
8556 process_input(sip, conn);
8559 /* Callback for new connections on incoming TCP port */
8560 static void sipe_newconn_cb(gpointer data, gint source,
8561 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8563 PurpleConnection *gc = data;
8564 struct sipe_account_data *sip = gc->proto_data;
8565 struct sip_connection *conn;
8567 int newfd = accept(source, NULL, NULL);
8569 conn = connection_create(sip, newfd);
8571 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8574 static void login_cb(gpointer data, gint source,
8575 SIPE_UNUSED_PARAMETER const gchar *error_message)
8577 PurpleConnection *gc = data;
8578 struct sipe_account_data *sip;
8579 struct sip_connection *conn;
8581 if (!PURPLE_CONNECTION_IS_VALID(gc))
8583 if (source >= 0)
8584 close(source);
8585 return;
8588 if (source < 0) {
8589 purple_connection_error(gc, _("Could not connect"));
8590 return;
8593 sip = gc->proto_data;
8594 sip->fd = source;
8595 sip->last_keepalive = time(NULL);
8597 conn = connection_create(sip, source);
8599 do_register(sip);
8601 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
8604 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
8605 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
8607 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
8608 if (sip == NULL) return;
8610 do_register(sip);
8613 static guint sipe_ht_hash_nick(const char *nick)
8615 char *lc = g_utf8_strdown(nick, -1);
8616 guint bucket = g_str_hash(lc);
8617 g_free(lc);
8619 return bucket;
8622 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8624 char *nick1_norm = NULL;
8625 char *nick2_norm = NULL;
8626 gboolean equal;
8628 if (nick1 == NULL && nick2 == NULL) return TRUE;
8629 if (nick1 == NULL || nick2 == NULL ||
8630 !g_utf8_validate(nick1, -1, NULL) ||
8631 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8633 nick1_norm = g_utf8_casefold(nick1, -1);
8634 nick2_norm = g_utf8_casefold(nick2, -1);
8635 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8636 g_free(nick2_norm);
8637 g_free(nick1_norm);
8639 return equal;
8642 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
8644 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8646 sip->listen_data = NULL;
8648 if (listenfd == -1) {
8649 purple_connection_error(sip->gc, _("Could not create listen socket"));
8650 return;
8653 sip->fd = listenfd;
8655 sip->listenport = purple_network_get_port_from_fd(sip->fd);
8656 sip->listenfd = sip->fd;
8658 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
8660 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
8661 do_register(sip);
8664 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
8665 SIPE_UNUSED_PARAMETER const char *error_message)
8667 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8669 sip->query_data = NULL;
8671 if (!hosts || !hosts->data) {
8672 purple_connection_error(sip->gc, _("Could not resolve hostname"));
8673 return;
8676 hosts = g_slist_remove(hosts, hosts->data);
8677 g_free(sip->serveraddr);
8678 sip->serveraddr = hosts->data;
8679 hosts = g_slist_remove(hosts, hosts->data);
8680 while (hosts) {
8681 void *tmp = hosts->data;
8682 hosts = g_slist_remove(hosts, tmp);
8683 hosts = g_slist_remove(hosts, tmp);
8684 g_free(tmp);
8687 /* create socket for incoming connections */
8688 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
8689 sipe_udp_host_resolved_listen_cb, sip);
8690 if (sip->listen_data == NULL) {
8691 purple_connection_error(sip->gc, _("Could not create listen socket"));
8692 return;
8696 static const struct sipe_service_data *current_service = NULL;
8698 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
8699 PurpleSslErrorType error,
8700 gpointer data)
8702 PurpleConnection *gc = data;
8703 struct sipe_account_data *sip;
8705 /* If the connection is already disconnected, we don't need to do anything else */
8706 if (!PURPLE_CONNECTION_IS_VALID(gc))
8707 return;
8709 sip = gc->proto_data;
8710 current_service = sip->service_data;
8711 if (current_service) {
8712 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8713 current_service->transport ? current_service->transport : "NULL",
8714 current_service->service ? current_service->service : "NULL");
8717 sip->fd = -1;
8718 sip->gsc = NULL;
8720 switch(error) {
8721 case PURPLE_SSL_CONNECT_FAILED:
8722 purple_connection_error(gc, _("Connection failed"));
8723 break;
8724 case PURPLE_SSL_HANDSHAKE_FAILED:
8725 purple_connection_error(gc, _("SSL handshake failed"));
8726 break;
8727 case PURPLE_SSL_CERTIFICATE_INVALID:
8728 purple_connection_error(gc, _("SSL certificate invalid"));
8729 break;
8733 static void
8734 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
8736 struct sipe_account_data *sip = (struct sipe_account_data*) data;
8737 PurpleProxyConnectData *connect_data;
8739 sip->listen_data = NULL;
8741 sip->listenfd = listenfd;
8742 if (sip->listenfd == -1) {
8743 purple_connection_error(sip->gc, _("Could not create listen socket"));
8744 return;
8747 SIPE_DEBUG_INFO("listenfd: %d", sip->listenfd);
8748 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8749 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8750 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
8751 sipe_newconn_cb, sip->gc);
8752 SIPE_DEBUG_INFO("connecting to %s port %d",
8753 sip->realhostname, sip->realport);
8754 /* open tcp connection to the server */
8755 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
8756 sip->realport, login_cb, sip->gc);
8758 if (connect_data == NULL) {
8759 purple_connection_error(sip->gc, _("Could not create socket"));
8763 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
8765 PurpleAccount *account = sip->account;
8766 PurpleConnection *gc = sip->gc;
8768 if (port == 0) {
8769 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
8772 sip->realhostname = hostname;
8773 sip->realport = port;
8775 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8776 hostname, port);
8778 /* TODO: is there a good default grow size? */
8779 if (sip->transport != SIPE_TRANSPORT_UDP)
8780 sip->txbuf = purple_circ_buffer_new(0);
8782 if (sip->transport == SIPE_TRANSPORT_TLS) {
8783 /* SSL case */
8784 if (!purple_ssl_is_supported()) {
8785 gc->wants_to_die = TRUE;
8786 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8787 return;
8790 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8792 sip->gsc = purple_ssl_connect(account, hostname, port,
8793 login_cb_ssl, sipe_ssl_connect_failure, gc);
8794 if (sip->gsc == NULL) {
8795 purple_connection_error(gc, _("Could not create SSL context"));
8796 return;
8798 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
8799 /* UDP case */
8800 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8802 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
8803 if (sip->query_data == NULL) {
8804 purple_connection_error(gc, _("Could not resolve hostname"));
8806 } else {
8807 /* TCP case */
8808 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8809 /* create socket for incoming connections */
8810 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
8811 sipe_tcp_connect_listen_cb, sip);
8812 if (sip->listen_data == NULL) {
8813 purple_connection_error(gc, _("Could not create listen socket"));
8814 return;
8819 /* Service list for autodection */
8820 static const struct sipe_service_data service_autodetect[] = {
8821 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8822 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8823 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8824 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8825 { NULL, NULL, 0 }
8828 /* Service list for SSL/TLS */
8829 static const struct sipe_service_data service_tls[] = {
8830 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8831 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8832 { NULL, NULL, 0 }
8835 /* Service list for TCP */
8836 static const struct sipe_service_data service_tcp[] = {
8837 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8838 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8839 { NULL, NULL, 0 }
8842 /* Service list for UDP */
8843 static const struct sipe_service_data service_udp[] = {
8844 { "sip", "udp", SIPE_TRANSPORT_UDP },
8845 { NULL, NULL, 0 }
8848 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8849 static void resolve_next_service(struct sipe_account_data *sip,
8850 const struct sipe_service_data *start)
8852 if (start) {
8853 sip->service_data = start;
8854 } else {
8855 sip->service_data++;
8856 if (sip->service_data->service == NULL) {
8857 gchar *hostname;
8858 /* Try connecting to the SIP hostname directly */
8859 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8860 if (sip->auto_transport) {
8861 // If SSL is supported, default to using it; OCS servers aren't configured
8862 // by default to accept TCP
8863 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8864 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
8865 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8868 hostname = g_strdup(sip->sipdomain);
8869 create_connection(sip, hostname, 0);
8870 return;
8874 /* Try to resolve next service */
8875 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8876 sip->service_data->transport,
8877 sip->sipdomain,
8878 srvresolved, sip);
8881 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8883 struct sipe_account_data *sip = data;
8885 sip->srv_query_data = NULL;
8887 /* find the host to connect to */
8888 if (results) {
8889 gchar *hostname = g_strdup(resp->hostname);
8890 int port = resp->port;
8891 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8892 hostname, port);
8893 g_free(resp);
8895 sip->transport = sip->service_data->type;
8897 create_connection(sip, hostname, port);
8898 } else {
8899 resolve_next_service(sip, NULL);
8903 static void sipe_login(PurpleAccount *account)
8905 PurpleConnection *gc;
8906 struct sipe_account_data *sip;
8907 gchar **signinname_login, **userserver;
8908 const char *transport;
8909 const char *email;
8911 const char *username = purple_account_get_username(account);
8912 gc = purple_account_get_connection(account);
8914 SIPE_DEBUG_INFO("sipe_login: username '%s'", username);
8916 if (strpbrk(username, "\t\v\r\n") != NULL) {
8917 gc->wants_to_die = TRUE;
8918 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
8919 return;
8922 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
8923 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
8924 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
8925 sip->gc = gc;
8926 sip->account = account;
8927 sip->reregister_set = FALSE;
8928 sip->reauthenticate_set = FALSE;
8929 sip->subscribed = FALSE;
8930 sip->subscribed_buddies = FALSE;
8931 sip->initial_state_published = FALSE;
8933 /* username format: <username>,[<optional login>] */
8934 signinname_login = g_strsplit(username, ",", 2);
8935 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login[0]);
8937 /* ensure that username format is name@domain */
8938 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
8939 g_strfreev(signinname_login);
8940 gc->wants_to_die = TRUE;
8941 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
8942 return;
8944 sip->username = g_strdup(signinname_login[0]);
8946 /* ensure that email format is name@domain if provided */
8947 email = purple_account_get_string(sip->account, "email", NULL);
8948 if (!is_empty(email) &&
8949 (!strchr(email, '@') || g_str_has_prefix(email, "@") || g_str_has_suffix(email, "@")))
8951 gc->wants_to_die = TRUE;
8952 purple_connection_error(gc, _("Email address should be valid if provided\nExample: user@company.com"));
8953 return;
8955 sip->email = !is_empty(email) ? g_strdup(email) : g_strdup(sip->username);
8957 /* login name specified? */
8958 if (signinname_login[1] && strlen(signinname_login[1])) {
8959 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
8960 gboolean has_domain = domain_user[1] != NULL;
8961 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login[1]);
8962 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
8963 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
8964 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8965 sip->authdomain ? sip->authdomain : "", sip->authuser);
8966 g_strfreev(domain_user);
8969 userserver = g_strsplit(signinname_login[0], "@", 2);
8970 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver[0], userserver[1]);
8971 purple_connection_set_display_name(gc, userserver[0]);
8972 sip->sipdomain = g_strdup(userserver[1]);
8973 g_strfreev(userserver);
8974 g_strfreev(signinname_login);
8976 if (strchr(sip->username, ' ') != NULL) {
8977 gc->wants_to_die = TRUE;
8978 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
8979 return;
8982 sip->password = g_strdup(purple_connection_get_password(gc));
8984 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8985 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8986 g_free, (GDestroyNotify)g_hash_table_destroy);
8987 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8988 g_free, (GDestroyNotify)sipe_subscription_free);
8990 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8992 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
8994 g_free(sip->status);
8995 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8997 sip->auto_transport = FALSE;
8998 transport = purple_account_get_string(account, "transport", "auto");
8999 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
9000 if (userserver[0]) {
9001 /* Use user specified server[:port] */
9002 int port = 0;
9004 if (userserver[1])
9005 port = atoi(userserver[1]);
9007 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
9008 userserver[0], port);
9010 if (sipe_strequal(transport, "auto")) {
9011 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
9012 } else if (sipe_strequal(transport, "tls")) {
9013 sip->transport = SIPE_TRANSPORT_TLS;
9014 } else if (sipe_strequal(transport, "tcp")) {
9015 sip->transport = SIPE_TRANSPORT_TCP;
9016 } else {
9017 sip->transport = SIPE_TRANSPORT_UDP;
9020 create_connection(sip, g_strdup(userserver[0]), port);
9021 } else {
9022 /* Server auto-discovery */
9023 if (sipe_strequal(transport, "auto")) {
9024 sip->auto_transport = TRUE;
9025 if (current_service && current_service->transport != NULL && current_service->service != NULL ){
9026 current_service++;
9027 resolve_next_service(sip, current_service);
9028 } else {
9029 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
9031 } else if (sipe_strequal(transport, "tls")) {
9032 resolve_next_service(sip, service_tls);
9033 } else if (sipe_strequal(transport, "tcp")) {
9034 resolve_next_service(sip, service_tcp);
9035 } else {
9036 resolve_next_service(sip, service_udp);
9039 g_strfreev(userserver);
9042 static void sipe_connection_cleanup(struct sipe_account_data *sip)
9044 connection_free_all(sip);
9046 g_free(sip->epid);
9047 sip->epid = NULL;
9049 if (sip->query_data != NULL)
9050 purple_dnsquery_destroy(sip->query_data);
9051 sip->query_data = NULL;
9053 if (sip->srv_query_data != NULL)
9054 purple_srv_cancel(sip->srv_query_data);
9055 sip->srv_query_data = NULL;
9057 if (sip->listen_data != NULL)
9058 purple_network_listen_cancel(sip->listen_data);
9059 sip->listen_data = NULL;
9061 if (sip->gsc != NULL)
9062 purple_ssl_close(sip->gsc);
9063 sip->gsc = NULL;
9065 sipe_auth_free(&sip->registrar);
9066 sipe_auth_free(&sip->proxy);
9068 if (sip->txbuf)
9069 purple_circ_buffer_destroy(sip->txbuf);
9070 sip->txbuf = NULL;
9072 g_free(sip->realhostname);
9073 sip->realhostname = NULL;
9075 g_free(sip->server_version);
9076 sip->server_version = NULL;
9078 if (sip->listenpa)
9079 purple_input_remove(sip->listenpa);
9080 sip->listenpa = 0;
9081 if (sip->tx_handler)
9082 purple_input_remove(sip->tx_handler);
9083 sip->tx_handler = 0;
9084 if (sip->resendtimeout)
9085 purple_timeout_remove(sip->resendtimeout);
9086 sip->resendtimeout = 0;
9087 if (sip->timeouts) {
9088 GSList *entry = sip->timeouts;
9089 while (entry) {
9090 struct scheduled_action *sched_action = entry->data;
9091 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
9092 purple_timeout_remove(sched_action->timeout_handler);
9093 if (sched_action->destroy) {
9094 (*sched_action->destroy)(sched_action->payload);
9096 g_free(sched_action->name);
9097 g_free(sched_action);
9098 entry = entry->next;
9101 g_slist_free(sip->timeouts);
9103 if (sip->allow_events) {
9104 GSList *entry = sip->allow_events;
9105 while (entry) {
9106 g_free(entry->data);
9107 entry = entry->next;
9110 g_slist_free(sip->allow_events);
9112 if (sip->containers) {
9113 GSList *entry = sip->containers;
9114 while (entry) {
9115 free_container((struct sipe_container *)entry->data);
9116 entry = entry->next;
9119 g_slist_free(sip->containers);
9121 if (sip->contact)
9122 g_free(sip->contact);
9123 sip->contact = NULL;
9124 if (sip->regcallid)
9125 g_free(sip->regcallid);
9126 sip->regcallid = NULL;
9128 if (sip->serveraddr)
9129 g_free(sip->serveraddr);
9130 sip->serveraddr = NULL;
9132 if (sip->focus_factory_uri)
9133 g_free(sip->focus_factory_uri);
9134 sip->focus_factory_uri = NULL;
9136 sip->fd = -1;
9137 sip->processing_input = FALSE;
9139 if (sip->ews) {
9140 sipe_ews_free(sip->ews);
9142 sip->ews = NULL;
9146 * A callback for g_hash_table_foreach_remove
9148 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
9149 SIPE_UNUSED_PARAMETER gpointer user_data)
9151 sipe_free_buddy((struct sipe_buddy *) buddy);
9153 /* We must return TRUE as the key/value have already been deleted */
9154 return(TRUE);
9157 static void sipe_close(PurpleConnection *gc)
9159 struct sipe_account_data *sip = gc->proto_data;
9161 if (sip) {
9162 /* leave all conversations */
9163 sipe_session_close_all(sip);
9164 sipe_session_remove_all(sip);
9166 if (sip->csta) {
9167 sip_csta_close(sip);
9170 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
9171 /* unsubscribe all */
9172 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
9174 /* unregister */
9175 do_register_exp(sip, 0);
9178 sipe_connection_cleanup(sip);
9179 g_free(sip->sipdomain);
9180 g_free(sip->username);
9181 g_free(sip->email);
9182 g_free(sip->password);
9183 g_free(sip->authdomain);
9184 g_free(sip->authuser);
9185 g_free(sip->status);
9186 g_free(sip->note);
9187 g_free(sip->user_states);
9189 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
9190 g_hash_table_destroy(sip->buddies);
9191 g_hash_table_destroy(sip->our_publications);
9192 g_hash_table_destroy(sip->user_state_publications);
9193 g_hash_table_destroy(sip->subscriptions);
9194 g_hash_table_destroy(sip->filetransfers);
9196 if (sip->groups) {
9197 GSList *entry = sip->groups;
9198 while (entry) {
9199 struct sipe_group *group = entry->data;
9200 g_free(group->name);
9201 g_free(group);
9202 entry = entry->next;
9205 g_slist_free(sip->groups);
9207 if (sip->our_publication_keys) {
9208 GSList *entry = sip->our_publication_keys;
9209 while (entry) {
9210 g_free(entry->data);
9211 entry = entry->next;
9214 g_slist_free(sip->our_publication_keys);
9216 while (sip->transactions)
9217 transactions_remove(sip, sip->transactions->data);
9219 g_free(gc->proto_data);
9220 gc->proto_data = NULL;
9223 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
9224 SIPE_UNUSED_PARAMETER void *user_data)
9226 PurpleAccount *acct = purple_connection_get_account(gc);
9227 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
9228 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
9229 if (conv == NULL)
9230 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
9231 purple_conversation_present(conv);
9232 g_free(id);
9235 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
9236 SIPE_UNUSED_PARAMETER void *user_data)
9239 purple_blist_request_add_buddy(purple_connection_get_account(gc),
9240 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
9243 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
9244 SIPE_UNUSED_PARAMETER struct transaction *trans)
9246 PurpleNotifySearchResults *results;
9247 PurpleNotifySearchColumn *column;
9248 sipe_xml *searchResults;
9249 const sipe_xml *mrow;
9250 int match_count = 0;
9251 gboolean more = FALSE;
9252 gchar *secondary;
9254 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
9256 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
9257 if (!searchResults) {
9258 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9259 return FALSE;
9262 results = purple_notify_searchresults_new();
9264 if (results == NULL) {
9265 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9266 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
9268 sipe_xml_free(searchResults);
9269 return FALSE;
9272 column = purple_notify_searchresults_column_new(_("User name"));
9273 purple_notify_searchresults_column_add(results, column);
9275 column = purple_notify_searchresults_column_new(_("Name"));
9276 purple_notify_searchresults_column_add(results, column);
9278 column = purple_notify_searchresults_column_new(_("Company"));
9279 purple_notify_searchresults_column_add(results, column);
9281 column = purple_notify_searchresults_column_new(_("Country"));
9282 purple_notify_searchresults_column_add(results, column);
9284 column = purple_notify_searchresults_column_new(_("Email"));
9285 purple_notify_searchresults_column_add(results, column);
9287 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
9288 GList *row = NULL;
9290 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
9291 row = g_list_append(row, g_strdup(uri_parts[1]));
9292 g_strfreev(uri_parts);
9294 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
9295 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
9296 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
9297 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
9299 purple_notify_searchresults_row_add(results, row);
9300 match_count++;
9303 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
9304 char *data = sipe_xml_data(mrow);
9305 more = (g_strcasecmp(data, "true") == 0);
9306 g_free(data);
9309 secondary = g_strdup_printf(
9310 dngettext(PACKAGE_NAME,
9311 "Found %d contact%s:",
9312 "Found %d contacts%s:", match_count),
9313 match_count, more ? _(" (more matched your query)") : "");
9315 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
9316 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
9317 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
9319 g_free(secondary);
9320 sipe_xml_free(searchResults);
9321 return TRUE;
9324 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9326 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
9327 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
9328 unsigned i = 0;
9330 if (!attrs) return;
9332 do {
9333 PurpleRequestField *field = entries->data;
9334 const char *id = purple_request_field_get_id(field);
9335 const char *value = purple_request_field_string_get_value(field);
9337 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
9339 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
9340 } while ((entries = g_list_next(entries)) != NULL);
9341 attrs[i] = NULL;
9343 if (i > 0) {
9344 struct sipe_account_data *sip = gc->proto_data;
9345 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
9346 gchar *query = g_strjoinv(NULL, attrs);
9347 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
9348 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
9349 send_soap_request_with_cb(sip, domain_uri, body,
9350 (TransCallback) process_search_contact_response, NULL);
9351 g_free(domain_uri);
9352 g_free(body);
9353 g_free(query);
9356 g_strfreev(attrs);
9359 static void sipe_show_find_contact(PurplePluginAction *action)
9361 PurpleConnection *gc = (PurpleConnection *) action->context;
9362 PurpleRequestFields *fields;
9363 PurpleRequestFieldGroup *group;
9364 PurpleRequestField *field;
9366 fields = purple_request_fields_new();
9367 group = purple_request_field_group_new(NULL);
9368 purple_request_fields_add_group(fields, group);
9370 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
9371 purple_request_field_group_add_field(group, field);
9372 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
9373 purple_request_field_group_add_field(group, field);
9374 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
9375 purple_request_field_group_add_field(group, field);
9376 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
9377 purple_request_field_group_add_field(group, field);
9379 purple_request_fields(gc,
9380 _("Search"),
9381 _("Search for a contact"),
9382 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
9383 fields,
9384 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
9385 _("_Cancel"), NULL,
9386 purple_connection_get_account(gc), NULL, NULL, gc);
9389 static void sipe_show_about_plugin(PurplePluginAction *action)
9391 PurpleConnection *gc = (PurpleConnection *) action->context;
9392 char *tmp = g_strdup_printf(
9394 * Non-translatable parts, like markup, are hard-coded
9395 * into the format string. This requires more translatable
9396 * texts but it makes the translations less error prone.
9398 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION " </font></b><br/>"
9399 "<br/>"
9400 /* 1 */ "%s:<br/>"
9401 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9402 "<li> - MS Office Communications Server 2007</li><br/>"
9403 "<li> - MS Live Communications Server 2005</li><br/>"
9404 "<li> - MS Live Communications Server 2003</li><br/>"
9405 "<li> - Reuters Messaging</li><br/>"
9406 "<br/>"
9407 /* 2 */ "%s: <a href=\"" PACKAGE_URL "\">" PACKAGE_URL "</a><br/>"
9408 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9409 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT "\">%s</a><br/>"
9410 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9411 /* 8 */ "%s: GPLv2+<br/>"
9412 "<br/>"
9413 /* 9 */ "%s:<br/>"
9414 " - CERN<br/>"
9415 " - Reuters Messaging network<br/>"
9416 " - Deutsche Bank<br/>"
9417 " - Merrill Lynch<br/>"
9418 " - Wachovia<br/>"
9419 " - Intel<br/>"
9420 " - Nokia<br/>"
9421 " - HP<br/>"
9422 " - Symantec<br/>"
9423 " - Accenture<br/>"
9424 " - Capgemini<br/>"
9425 " - Siemens<br/>"
9426 " - Alcatel-Lucent<br/>"
9427 " - BT<br/>"
9428 "<br/>"
9429 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9430 "<br/>"
9431 /* 12 */ "<b>%s:</b><br/>"
9432 " - Anibal Avelar<br/>"
9433 " - Gabriel Burt<br/>"
9434 " - Stefan Becker<br/>"
9435 " - pier11<br/>"
9436 " - Jakub Adam<br/>"
9437 " - Tomáš Hrabčík<br/>"
9438 "<br/>"
9439 /* 13 */ "%s<br/>"
9441 /* The next 13 texts make up the SIPE about note text */
9442 /* About note, part 1/13: introduction */
9443 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9444 /* About note, part 2/13: home page URL (label) */
9445 _("Home"),
9446 /* About note, part 3/13: support forum URL (label) */
9447 _("Support"),
9448 /* About note, part 4/13: support forum name (hyperlink text) */
9449 _("Help Forum"),
9450 /* About note, part 5/13: bug tracker URL (label) */
9451 _("Report Problems"),
9452 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9453 _("Bug Tracker"),
9454 /* About note, part 7/13: translation service URL (label) */
9455 _("Translations"),
9456 /* About note, part 8/13: license type (label) */
9457 _("License"),
9458 /* About note, part 9/13: known users */
9459 _("We support users in such organizations as"),
9460 /* About note, part 10/13: translation request, text before Transifex.net URL */
9461 /* append a space if text is not empty */
9462 _("Please help us to translate SIPE to your native language here at "),
9463 /* About note, part 11/13: translation request, text after Transifex.net URL */
9464 /* start with a space if text is not empty */
9465 _(" using convenient web interface"),
9466 /* About note, part 12/13: author list (header) */
9467 _("Authors"),
9468 /* About note, part 13/13: Localization credit */
9469 /* PLEASE NOTE: do *NOT* simply translate the english original */
9470 /* but write something similar to the following sentence: */
9471 /* "Localization for <language name> (<language code>): <name>" */
9472 _("Original texts in English (en): SIPE developers")
9474 purple_notify_formatted(gc, NULL, " ", NULL, tmp, NULL, NULL);
9475 g_free(tmp);
9478 static void sipe_republish_calendar(PurplePluginAction *action)
9480 PurpleConnection *gc = (PurpleConnection *) action->context;
9481 struct sipe_account_data *sip = gc->proto_data;
9483 sipe_update_calendar(sip);
9486 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
9487 gpointer value,
9488 GString* str)
9490 struct sipe_publication *publication = value;
9492 g_string_append_printf( str,
9493 SIPE_PUB_XML_PUBLICATION_CLEAR,
9494 publication->category,
9495 publication->instance,
9496 publication->container,
9497 publication->version,
9498 "static");
9501 static void sipe_reset_status(PurplePluginAction *action)
9503 PurpleConnection *gc = (PurpleConnection *) action->context;
9504 struct sipe_account_data *sip = gc->proto_data;
9506 if (sip->ocs2007) /* 2007+ */
9508 GString* str = g_string_new(NULL);
9509 gchar *publications;
9511 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
9512 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9513 return;
9516 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
9517 publications = g_string_free(str, FALSE);
9519 send_presence_publish(sip, publications);
9520 g_free(publications);
9522 else /* 2005 */
9524 send_presence_soap0(sip, FALSE, TRUE);
9528 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
9529 gpointer context)
9531 PurpleConnection *gc = (PurpleConnection *)context;
9532 struct sipe_account_data *sip = gc->proto_data;
9533 GList *menu = NULL;
9534 PurplePluginAction *act;
9535 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
9537 act = purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin);
9538 menu = g_list_prepend(menu, act);
9540 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
9541 menu = g_list_prepend(menu, act);
9543 if (sipe_strequal(calendar, "EXCH")) {
9544 act = purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar);
9545 menu = g_list_prepend(menu, act);
9548 act = purple_plugin_action_new(_("Reset status"), sipe_reset_status);
9549 menu = g_list_prepend(menu, act);
9551 menu = g_list_reverse(menu);
9553 return menu;
9556 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
9560 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9562 return TRUE;
9566 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
9568 return TRUE;
9572 static char *sipe_status_text(PurpleBuddy *buddy)
9574 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9575 const PurpleStatus *status = purple_presence_get_active_status(presence);
9576 const char *status_id = purple_status_get_id(status);
9577 struct sipe_account_data *sip = (struct sipe_account_data *)buddy->account->gc->proto_data;
9578 struct sipe_buddy *sbuddy;
9579 char *text = NULL;
9581 if (!sip) return NULL; /* happens on pidgin exit */
9583 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9584 if (sbuddy) {
9585 const char *activity_str = sbuddy->activity ?
9586 sbuddy->activity :
9587 sipe_strequal(status_id, SIPE_STATUS_ID_BUSY) || sipe_strequal(status_id, SIPE_STATUS_ID_BRB) ?
9588 purple_status_get_name(status) : NULL;
9590 if (activity_str && sbuddy->note)
9592 text = g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
9594 else if (activity_str)
9596 text = g_strdup(activity_str);
9598 else if (sbuddy->note)
9600 text = g_strdup_printf("<i>%s</i>", sbuddy->note);
9604 return text;
9607 /** for Access levels menu */
9608 #define INDENT_FMT " %s"
9610 /** Member is directly placed to access level container.
9611 * For example SIP URI of user is in the container.
9613 #define INDENT_MARKED_FMT "* %s"
9615 /** Member is indirectly belong to access level container.
9616 * For example 'sameEnterprise' is in the container and user
9617 * belongs to that same enterprise.
9619 #define INDENT_MARKED_INHERITED_FMT "= %s"
9621 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
9623 const PurplePresence *presence = purple_buddy_get_presence(buddy);
9624 const PurpleStatus *status = purple_presence_get_active_status(presence);
9625 struct sipe_account_data *sip;
9626 struct sipe_buddy *sbuddy;
9627 char *note = NULL;
9628 gboolean is_oof_note = FALSE;
9629 char *activity = NULL;
9630 char *calendar = NULL;
9631 char *meeting_subject = NULL;
9632 char *meeting_location = NULL;
9634 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
9635 if (sip) //happens on pidgin exit
9637 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
9638 if (sbuddy)
9640 note = sbuddy->note;
9641 is_oof_note = sbuddy->is_oof_note;
9642 activity = sbuddy->activity;
9643 calendar = sipe_cal_get_description(sbuddy);
9644 meeting_subject = sbuddy->meeting_subject;
9645 meeting_location = sbuddy->meeting_location;
9649 //Layout
9650 if (purple_presence_is_online(presence))
9652 const char *status_str = activity ? activity : purple_status_get_name(status);
9654 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
9656 if (purple_presence_is_online(presence) &&
9657 !is_empty(calendar))
9659 purple_notify_user_info_add_pair(user_info, _("Calendar"), calendar);
9661 g_free(calendar);
9662 if (!is_empty(meeting_location))
9664 purple_notify_user_info_add_pair(user_info, _("Meeting in"), meeting_location);
9666 if (!is_empty(meeting_subject))
9668 purple_notify_user_info_add_pair(user_info, _("Meeting about"), meeting_subject);
9671 if (note)
9673 char *tmp = g_strdup_printf("<i>%s</i>", note);
9674 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy->name, note);
9676 purple_notify_user_info_add_pair(user_info, is_oof_note ? _("Out of office note") : _("Note"), tmp);
9677 g_free(tmp);
9680 if (sip && sip->ocs2007) {
9681 gboolean is_group_access = FALSE;
9682 const int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(buddy->name), &is_group_access);
9683 const char *access_level = sipe_get_access_level_name(container_id);
9684 char *text = is_group_access ?
9685 g_strdup(access_level) :
9686 g_strdup_printf(INDENT_MARKED_FMT, access_level);
9688 purple_notify_user_info_add_pair(user_info, _("Access level"), text);
9689 g_free(text);
9693 #if PURPLE_VERSION_CHECK(2,5,0)
9694 static GHashTable *
9695 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
9697 GHashTable *table;
9698 table = g_hash_table_new(g_str_hash, g_str_equal);
9699 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
9700 return table;
9702 #endif
9704 static PurpleBuddy *
9705 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
9707 PurpleBuddy *clone;
9708 const gchar *server_alias, *email;
9709 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
9711 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
9713 purple_blist_add_buddy(clone, NULL, group, NULL);
9715 server_alias = purple_buddy_get_server_alias(buddy);
9716 if (server_alias) {
9717 purple_blist_server_alias_buddy(clone, server_alias);
9720 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9721 if (email) {
9722 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
9725 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
9726 //for UI to update;
9727 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
9728 return clone;
9731 static void
9732 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
9734 PurpleBuddy *buddy, *b;
9735 PurpleConnection *gc;
9736 PurpleGroup * group = purple_find_group(group_name);
9738 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
9740 buddy = (PurpleBuddy *)node;
9742 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
9743 gc = purple_account_get_connection(buddy->account);
9745 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
9746 if (!b){
9747 purple_blist_add_buddy_clone(group, buddy);
9750 sipe_group_buddy(gc, buddy->name, NULL, group_name);
9753 static void
9754 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
9756 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9758 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
9760 /* 2007+ conference */
9761 if (sip->ocs2007)
9763 sipe_conf_add(sip, buddy->name);
9765 else /* 2005- multiparty chat */
9767 gchar *self = sip_uri_self(sip);
9768 struct sip_session *session;
9770 session = sipe_session_add_chat(sip);
9771 session->chat_title = sipe_chat_get_name(session->callid);
9772 session->roster_manager = g_strdup(self);
9774 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
9775 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
9776 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
9777 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
9779 g_free(self);
9783 static gboolean
9784 sipe_is_election_finished(struct sip_session *session)
9786 gboolean res = TRUE;
9788 SIPE_DIALOG_FOREACH {
9789 if (dialog->election_vote == 0) {
9790 res = FALSE;
9791 break;
9793 } SIPE_DIALOG_FOREACH_END;
9795 if (res) {
9796 session->is_voting_in_progress = FALSE;
9798 return res;
9801 static void
9802 sipe_election_start(struct sipe_account_data *sip,
9803 struct sip_session *session)
9805 int election_timeout;
9807 if (session->is_voting_in_progress) {
9808 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9809 return;
9810 } else {
9811 session->is_voting_in_progress = TRUE;
9813 session->bid = rand();
9815 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
9817 SIPE_DIALOG_FOREACH {
9818 /* reset election_vote for each chat participant */
9819 dialog->election_vote = 0;
9821 /* send RequestRM to each chat participant*/
9822 sipe_send_election_request_rm(sip, dialog, session->bid);
9823 } SIPE_DIALOG_FOREACH_END;
9825 election_timeout = 15; /* sec */
9826 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
9830 * @param who a URI to whom to invite to chat
9832 void
9833 sipe_invite_to_chat(struct sipe_account_data *sip,
9834 struct sip_session *session,
9835 const gchar *who)
9837 /* a conference */
9838 if (session->focus_uri)
9840 sipe_invite_conf(sip, session, who);
9842 else /* a multi-party chat */
9844 gchar *self = sip_uri_self(sip);
9845 if (session->roster_manager) {
9846 if (sipe_strcase_equal(session->roster_manager, self)) {
9847 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
9848 } else {
9849 sipe_refer(sip, session, who);
9851 } else {
9852 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9854 session->pending_invite_queue = slist_insert_unique_sorted(
9855 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
9857 sipe_election_start(sip, session);
9859 g_free(self);
9863 void
9864 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
9865 struct sip_session *session)
9867 gchar *invitee;
9868 GSList *entry = session->pending_invite_queue;
9870 while (entry) {
9871 invitee = entry->data;
9872 sipe_invite_to_chat(sip, session, invitee);
9873 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
9874 g_free(invitee);
9878 static void
9879 sipe_election_result(struct sipe_account_data *sip,
9880 void *sess)
9882 struct sip_session *session = (struct sip_session *)sess;
9883 gchar *rival;
9884 gboolean has_won = TRUE;
9886 if (session->roster_manager) {
9887 SIPE_DEBUG_INFO(
9888 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9889 session->roster_manager);
9890 return;
9893 session->is_voting_in_progress = FALSE;
9895 SIPE_DIALOG_FOREACH {
9896 if (dialog->election_vote < 0) {
9897 has_won = FALSE;
9898 rival = dialog->with;
9899 break;
9901 } SIPE_DIALOG_FOREACH_END;
9903 if (has_won) {
9904 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9906 session->roster_manager = sip_uri_self(sip);
9908 SIPE_DIALOG_FOREACH {
9909 /* send SetRM to each chat participant*/
9910 sipe_send_election_set_rm(sip, dialog);
9911 } SIPE_DIALOG_FOREACH_END;
9912 } else {
9913 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
9915 session->bid = 0;
9917 sipe_process_pending_invite_queue(sip, session);
9921 * For 2007+ conference only.
9923 static void
9924 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9926 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9927 struct sip_session *session;
9929 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9930 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9932 session = sipe_session_find_chat_by_title(sip, chat_title);
9934 sipe_conf_modify_user_role(sip, session, buddy->name);
9938 * For 2007+ conference only.
9940 static void
9941 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9943 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9944 struct sip_session *session;
9946 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9947 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9949 session = sipe_session_find_chat_by_title(sip, chat_title);
9951 sipe_conf_delete_user(sip, session, buddy->name);
9954 static void
9955 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9957 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9958 struct sip_session *session;
9960 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9961 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9963 session = sipe_session_find_chat_by_title(sip, chat_title);
9965 sipe_invite_to_chat(sip, session, buddy->name);
9968 static void
9969 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9971 struct sipe_account_data *sip = buddy->account->gc->proto_data;
9973 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9974 if (phone) {
9975 char *tel_uri = sip_to_tel_uri(phone);
9977 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9978 sip_csta_make_call(sip, tel_uri);
9980 g_free(tel_uri);
9984 static void
9985 sipe_open_url(const char *url)
9987 const char *util;
9988 char *command_line;
9990 if (!url) return;
9992 #ifdef _WIN32
9993 util = "cmd /c start";
9994 #else
9995 util = g_str_has_prefix(url, "mailto:") ? "xdg-email" : "xdg-open";
9996 #endif
9998 command_line = g_strdup_printf("%s %s", util, url);
9999 g_spawn_command_line_async(command_line, NULL);
10000 g_free(command_line);
10003 static void
10004 sipe_buddy_menu_access_level_help_cb(SIPE_UNUSED_PARAMETER PurpleBuddy *buddy)
10006 /** Translators: replace with URL to localized page
10007 * If it doesn't exist copy the original URL */
10008 sipe_open_url(_("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
10011 static void
10012 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
10014 const gchar *email;
10015 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
10017 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
10018 if (email)
10020 char *mailto = g_strdup_printf("mailto:%s", email);
10021 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s", email);
10022 sipe_open_url(mailto);
10023 g_free(mailto);
10025 else
10027 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
10031 static void
10032 sipe_buddy_menu_access_level_cb(SIPE_UNUSED_PARAMETER PurpleBuddy *buddy,
10033 struct sipe_container *container)
10035 struct sipe_account_data *sip = buddy->account->gc->proto_data;
10036 struct sipe_container_member *member;
10038 if (!container || !container->members) return;
10040 member = ((struct sipe_container_member *)container->members->data);
10042 if (!member->type) return;
10044 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
10045 container->id, member->type, member->value ? member->value : "");
10047 sipe_change_access_level(sip, container->id, member->type, member->value);
10050 static GList *
10051 sipe_get_access_control_menu(struct sipe_account_data *sip,
10052 const char* uri);
10055 * A menu which appear when right-clicking on buddy in contact list.
10057 static GList *
10058 sipe_buddy_menu(PurpleBuddy *buddy)
10060 PurpleBlistNode *g_node;
10061 PurpleGroup *group, *gr_parent;
10062 PurpleMenuAction *act;
10063 GList *menu = NULL;
10064 GList *menu_groups = NULL;
10065 struct sipe_account_data *sip = buddy->account->gc->proto_data;
10066 const char *email;
10067 const char *phone;
10068 const char *phone_disp_str;
10069 gchar *self = sip_uri_self(sip);
10071 SIPE_SESSION_FOREACH {
10072 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
10074 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
10076 PurpleConvChatBuddyFlags flags;
10077 PurpleConvChatBuddyFlags flags_us;
10079 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
10080 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10081 if (session->focus_uri
10082 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
10083 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10085 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
10086 act = purple_menu_action_new(label,
10087 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
10088 session->chat_title, NULL);
10089 g_free(label);
10090 menu = g_list_prepend(menu, act);
10093 if (session->focus_uri
10094 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10096 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
10097 act = purple_menu_action_new(label,
10098 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
10099 session->chat_title, NULL);
10100 g_free(label);
10101 menu = g_list_prepend(menu, act);
10104 else
10106 if (!session->focus_uri
10107 || (session->focus_uri && !session->locked))
10109 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
10110 act = purple_menu_action_new(label,
10111 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
10112 session->chat_title, NULL);
10113 g_free(label);
10114 menu = g_list_prepend(menu, act);
10118 } SIPE_SESSION_FOREACH_END;
10120 act = purple_menu_action_new(_("New chat"),
10121 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
10122 NULL, NULL);
10123 menu = g_list_prepend(menu, act);
10125 if (sip->csta && !sip->csta->line_status) {
10126 gchar *tmp = NULL;
10127 /* work phone */
10128 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
10129 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
10130 if (phone) {
10131 gchar *label = g_strdup_printf(_("Work %s"),
10132 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10133 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10134 g_free(tmp);
10135 tmp = NULL;
10136 g_free(label);
10137 menu = g_list_prepend(menu, act);
10140 /* mobile phone */
10141 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
10142 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
10143 if (phone) {
10144 gchar *label = g_strdup_printf(_("Mobile %s"),
10145 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10146 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10147 g_free(tmp);
10148 tmp = NULL;
10149 g_free(label);
10150 menu = g_list_prepend(menu, act);
10153 /* home phone */
10154 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
10155 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
10156 if (phone) {
10157 gchar *label = g_strdup_printf(_("Home %s"),
10158 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10159 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10160 g_free(tmp);
10161 tmp = NULL;
10162 g_free(label);
10163 menu = g_list_prepend(menu, act);
10166 /* other phone */
10167 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
10168 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
10169 if (phone) {
10170 gchar *label = g_strdup_printf(_("Other %s"),
10171 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10172 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10173 g_free(tmp);
10174 tmp = NULL;
10175 g_free(label);
10176 menu = g_list_prepend(menu, act);
10179 /* custom1 phone */
10180 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
10181 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
10182 if (phone) {
10183 gchar *label = g_strdup_printf(_("Custom1 %s"),
10184 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
10185 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
10186 g_free(tmp);
10187 tmp = NULL;
10188 g_free(label);
10189 menu = g_list_prepend(menu, act);
10193 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
10194 if (email) {
10195 act = purple_menu_action_new(_("Send email..."),
10196 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
10197 NULL, NULL);
10198 menu = g_list_prepend(menu, act);
10201 /* Access Level */
10202 if (sip->ocs2007) {
10203 GList *menu_access_levels = sipe_get_access_control_menu(sip, buddy->name);
10205 act = purple_menu_action_new(_("Access level"),
10206 NULL,
10207 NULL, menu_access_levels);
10208 menu = g_list_prepend(menu, act);
10211 /* Copy to */
10212 gr_parent = purple_buddy_get_group(buddy);
10213 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
10214 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
10215 continue;
10217 group = (PurpleGroup *)g_node;
10218 if (group == gr_parent)
10219 continue;
10221 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
10222 continue;
10224 act = purple_menu_action_new(purple_group_get_name(group),
10225 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
10226 group->name, NULL);
10227 menu_groups = g_list_prepend(menu_groups, act);
10229 menu_groups = g_list_reverse(menu_groups);
10231 act = purple_menu_action_new(_("Copy to"),
10232 NULL,
10233 NULL, menu_groups);
10234 menu = g_list_prepend(menu, act);
10236 menu = g_list_reverse(menu);
10238 g_free(self);
10239 return menu;
10242 static void
10243 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
10245 struct sipe_account_data *sip = gc->proto_data;
10246 const char *domain = purple_request_fields_get_string(fields, "access_domain");
10247 int index = purple_request_fields_get_choice(fields, "container_id");
10248 /* move Blocked first */
10249 int i = (index == 4) ? 0 : index + 1;
10250 int container_id = containers[i];
10252 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
10254 sipe_change_access_level(sip, container_id, "domain", domain);
10257 static void
10258 sipe_ask_access_domain(struct sipe_account_data *sip)
10260 PurpleAccount *account = sip->account;
10261 PurpleConnection *gc = sip->gc;
10262 PurpleRequestFields *fields;
10263 PurpleRequestFieldGroup *g;
10264 PurpleRequestField *f;
10266 fields = purple_request_fields_new();
10268 g = purple_request_field_group_new(NULL);
10269 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
10270 purple_request_field_set_required(f, TRUE);
10271 purple_request_field_group_add_field(g, f);
10273 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
10274 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
10275 purple_request_field_choice_add(f, _("Team"));
10276 purple_request_field_choice_add(f, _("Company"));
10277 purple_request_field_choice_add(f, _("Public"));
10278 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
10279 purple_request_field_choice_set_default_value(f, 3); /* index */
10280 purple_request_field_set_required(f, TRUE);
10281 purple_request_field_group_add_field(g, f);
10283 purple_request_fields_add_group(fields, g);
10285 purple_request_fields(gc, _("Add new domain"),
10286 _("Add new domain"), NULL, fields,
10287 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
10288 _("Cancel"), NULL,
10289 account, NULL, NULL, gc);
10292 static void
10293 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
10295 sipe_ask_access_domain((struct sipe_account_data *)buddy->account->gc->proto_data);
10298 static GList *
10299 sipe_get_access_levels_menu(struct sipe_account_data *sip,
10300 const char* member_type,
10301 const char* member_value,
10302 const gboolean extra_menu)
10304 GList *menu_access_levels = NULL;
10305 unsigned int i;
10306 char *menu_name;
10307 PurpleMenuAction *act;
10308 struct sipe_container *container;
10309 struct sipe_container_member *member;
10310 gboolean is_group_access = FALSE;
10311 int container_id = sipe_find_access_level(sip, member_type, member_value, &is_group_access);
10313 for (i = 1; i <= CONTAINERS_LEN; i++) {
10314 /* to put Blocked level last in menu list.
10315 * Blocked should remaim in the first place in the containers[] array.
10317 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
10318 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
10320 container = g_new0(struct sipe_container, 1);
10321 member = g_new0(struct sipe_container_member, 1);
10322 container->id = containers[j];
10323 container->members = g_slist_append(container->members, member);
10324 member->type = g_strdup(member_type);
10325 member->value = g_strdup(member_value);
10327 /* current container/access level */
10328 if (((int)containers[j]) == container_id) {
10329 menu_name = is_group_access ?
10330 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
10331 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
10332 } else {
10333 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
10336 act = purple_menu_action_new(menu_name,
10337 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10338 container, NULL);
10339 g_free(menu_name);
10340 menu_access_levels = g_list_prepend(menu_access_levels, act);
10343 if (extra_menu && (container_id >= 0)) {
10344 /* separator */
10345 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
10346 menu_access_levels = g_list_prepend(menu_access_levels, act);
10348 if (!is_group_access) {
10349 container = g_new0(struct sipe_container, 1);
10350 member = g_new0(struct sipe_container_member, 1);
10351 container->id = -1;
10352 container->members = g_slist_append(container->members, member);
10353 member->type = g_strdup(member_type);
10354 member->value = g_strdup(member_value);
10356 /* Translators: remove (clear) previously assigned access level */
10357 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
10358 act = purple_menu_action_new(menu_name,
10359 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
10360 container, NULL);
10361 g_free(menu_name);
10362 menu_access_levels = g_list_prepend(menu_access_levels, act);
10366 menu_access_levels = g_list_reverse(menu_access_levels);
10367 return menu_access_levels;
10370 static GList *
10371 sipe_get_access_groups_menu(struct sipe_account_data *sip)
10373 GList *menu_access_groups = NULL;
10374 PurpleMenuAction *act;
10375 GSList *access_domains = NULL;
10376 GSList *entry;
10377 char *menu_name;
10378 char *domain;
10380 act = purple_menu_action_new(_("People in my company"),
10381 NULL,
10382 NULL, sipe_get_access_levels_menu(sip, "sameEnterprise", NULL, FALSE));
10383 menu_access_groups = g_list_prepend(menu_access_groups, act);
10385 /* this is original name, don't edit */
10386 act = purple_menu_action_new(_("People in domains connected with my company"),
10387 NULL,
10388 NULL, sipe_get_access_levels_menu(sip, "federated", NULL, FALSE));
10389 menu_access_groups = g_list_prepend(menu_access_groups, act);
10391 act = purple_menu_action_new(_("People in public domains"),
10392 NULL,
10393 NULL, sipe_get_access_levels_menu(sip, "publicCloud", NULL, TRUE));
10394 menu_access_groups = g_list_prepend(menu_access_groups, act);
10396 access_domains = sipe_get_access_domains(sip);
10397 entry = access_domains;
10398 while (entry) {
10399 domain = entry->data;
10401 menu_name = g_strdup_printf(_("People at %s"), domain);
10402 act = purple_menu_action_new(menu_name,
10403 NULL,
10404 NULL, sipe_get_access_levels_menu(sip, "domain", g_strdup(domain), TRUE));
10405 menu_access_groups = g_list_prepend(menu_access_groups, act);
10406 g_free(menu_name);
10408 entry = entry->next;
10411 /* separator */
10412 /* People in domains connected with my company */
10413 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
10414 menu_access_groups = g_list_prepend(menu_access_groups, act);
10416 act = purple_menu_action_new(_("Add new domain"),
10417 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
10418 NULL, NULL);
10419 menu_access_groups = g_list_prepend(menu_access_groups, act);
10421 menu_access_groups = g_list_reverse(menu_access_groups);
10423 return menu_access_groups;
10426 static GList *
10427 sipe_get_access_control_menu(struct sipe_account_data *sip,
10428 const char* uri)
10430 GList *menu_access_levels = NULL;
10431 GList *menu_access_groups = NULL;
10432 char *menu_name;
10433 PurpleMenuAction *act;
10435 menu_access_levels = sipe_get_access_levels_menu(sip, "user", sipe_get_no_sip_uri(uri), TRUE);
10437 menu_access_groups = sipe_get_access_groups_menu(sip);
10439 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
10440 act = purple_menu_action_new(menu_name,
10441 NULL,
10442 NULL, menu_access_groups);
10443 g_free(menu_name);
10444 menu_access_levels = g_list_append(menu_access_levels, act);
10446 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
10447 act = purple_menu_action_new(menu_name,
10448 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
10449 NULL, NULL);
10450 g_free(menu_name);
10451 menu_access_levels = g_list_append(menu_access_levels, act);
10453 return menu_access_levels;
10456 static void
10457 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
10459 struct sipe_account_data *sip = chat->account->gc->proto_data;
10460 struct sip_session *session;
10462 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10463 sipe_conf_modify_conference_lock(sip, session, locked);
10466 static void
10467 sipe_chat_menu_unlock_cb(PurpleChat *chat)
10469 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
10470 sipe_conf_modify_lock(chat, FALSE);
10473 static void
10474 sipe_chat_menu_lock_cb(PurpleChat *chat)
10476 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
10477 sipe_conf_modify_lock(chat, TRUE);
10480 static GList *
10481 sipe_chat_menu(PurpleChat *chat)
10483 PurpleMenuAction *act;
10484 PurpleConvChatBuddyFlags flags_us;
10485 GList *menu = NULL;
10486 struct sipe_account_data *sip = chat->account->gc->proto_data;
10487 struct sip_session *session;
10488 gchar *self;
10490 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
10491 if (!session) return NULL;
10493 self = sip_uri_self(sip);
10494 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
10496 if (session->focus_uri
10497 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
10499 if (session->locked) {
10500 act = purple_menu_action_new(_("Unlock"),
10501 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
10502 NULL, NULL);
10503 menu = g_list_prepend(menu, act);
10504 } else {
10505 act = purple_menu_action_new(_("Lock"),
10506 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
10507 NULL, NULL);
10508 menu = g_list_prepend(menu, act);
10512 menu = g_list_reverse(menu);
10514 g_free(self);
10515 return menu;
10518 static GList *
10519 sipe_blist_node_menu(PurpleBlistNode *node)
10521 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
10522 return sipe_buddy_menu((PurpleBuddy *) node);
10523 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
10524 return sipe_chat_menu((PurpleChat *)node);
10525 } else {
10526 return NULL;
10530 static gboolean
10531 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
10533 char *uri = trans->payload->data;
10535 PurpleNotifyUserInfo *info;
10536 PurpleBuddy *pbuddy = NULL;
10537 struct sipe_buddy *sbuddy;
10538 const char *alias = NULL;
10539 char *device_name = NULL;
10540 char *server_alias = NULL;
10541 char *phone_number = NULL;
10542 char *email = NULL;
10543 const char *site;
10544 char *first_name = NULL;
10545 char *last_name = NULL;
10547 if (!sip) return FALSE;
10549 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
10551 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
10552 alias = purple_buddy_get_local_alias(pbuddy);
10554 //will query buddy UA's capabilities and send answer to log
10555 sipe_options_request(sip, uri);
10557 sbuddy = g_hash_table_lookup(sip->buddies, uri);
10558 if (sbuddy) {
10559 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
10562 info = purple_notify_user_info_new();
10564 if (msg->response != 200) {
10565 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
10566 } else {
10567 sipe_xml *searchResults;
10568 const sipe_xml *mrow;
10570 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
10571 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
10572 if (!searchResults) {
10573 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10574 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
10575 const char *value;
10576 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
10577 email = g_strdup(sipe_xml_attribute(mrow, "email"));
10578 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
10580 /* For 2007 system we will take this from ContactCard -
10581 * it has cleaner tel: URIs at least
10583 if (!sip->ocs2007) {
10584 char *tel_uri = sip_to_tel_uri(phone_number);
10585 /* trims its parameters, so call first */
10586 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
10587 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
10588 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
10589 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
10590 g_free(tel_uri);
10593 if (server_alias && strlen(server_alias) > 0) {
10594 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10596 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
10597 purple_notify_user_info_add_pair(info, _("Job title"), value);
10599 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
10600 purple_notify_user_info_add_pair(info, _("Office"), value);
10602 if (phone_number && strlen(phone_number) > 0) {
10603 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
10605 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
10606 purple_notify_user_info_add_pair(info, _("Company"), value);
10608 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
10609 purple_notify_user_info_add_pair(info, _("City"), value);
10611 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
10612 purple_notify_user_info_add_pair(info, _("State"), value);
10614 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
10615 purple_notify_user_info_add_pair(info, _("Country"), value);
10617 if (email && strlen(email) > 0) {
10618 purple_notify_user_info_add_pair(info, _("Email address"), email);
10622 sipe_xml_free(searchResults);
10625 purple_notify_user_info_add_section_break(info);
10627 if (is_empty(server_alias)) {
10628 g_free(server_alias);
10629 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
10630 if (server_alias) {
10631 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
10635 /* present alias if it differs from server alias */
10636 if (alias && !sipe_strequal(alias, server_alias))
10638 purple_notify_user_info_add_pair(info, _("Alias"), alias);
10641 if (is_empty(email)) {
10642 g_free(email);
10643 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
10644 if (email) {
10645 purple_notify_user_info_add_pair(info, _("Email address"), email);
10649 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
10650 if (site) {
10651 purple_notify_user_info_add_pair(info, _("Site"), site);
10654 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
10655 if (first_name && last_name) {
10656 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
10658 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
10659 g_free(link);
10661 g_free(first_name);
10662 g_free(last_name);
10664 if (device_name) {
10665 purple_notify_user_info_add_pair(info, _("Device"), device_name);
10668 /* show a buddy's user info in a nice dialog box */
10669 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
10670 uri, /* buddy's URI */
10671 info, /* body */
10672 NULL, /* callback called when dialog closed */
10673 NULL); /* userdata for callback */
10675 g_free(phone_number);
10676 g_free(server_alias);
10677 g_free(email);
10678 g_free(device_name);
10680 return TRUE;
10684 * AD search first, LDAP based
10686 static void sipe_get_info(PurpleConnection *gc, const char *username)
10688 struct sipe_account_data *sip = gc->proto_data;
10689 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
10690 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
10691 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
10692 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
10694 payload->destroy = g_free;
10695 payload->data = g_strdup(username);
10697 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
10698 send_soap_request_with_cb(sip, domain_uri, body,
10699 (TransCallback) process_get_info_response, payload);
10700 g_free(domain_uri);
10701 g_free(body);
10702 g_free(row);
10705 PurplePluginProtocolInfo prpl_info =
10707 OPT_PROTO_CHAT_TOPIC,
10708 NULL, /* user_splits */
10709 NULL, /* protocol_options */
10710 NO_BUDDY_ICONS, /* icon_spec */
10711 sipe_list_icon, /* list_icon */
10712 NULL, /* list_emblems */
10713 sipe_status_text, /* status_text */
10714 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
10715 sipe_status_types, /* away_states */
10716 sipe_blist_node_menu, /* blist_node_menu */
10717 NULL, /* chat_info */
10718 NULL, /* chat_info_defaults */
10719 sipe_login, /* login */
10720 sipe_close, /* close */
10721 sipe_im_send, /* send_im */
10722 NULL, /* set_info */ // TODO maybe
10723 sipe_send_typing, /* send_typing */
10724 sipe_get_info, /* get_info */
10725 sipe_set_status, /* set_status */
10726 sipe_set_idle, /* set_idle */
10727 NULL, /* change_passwd */
10728 sipe_add_buddy, /* add_buddy */
10729 NULL, /* add_buddies */
10730 sipe_remove_buddy, /* remove_buddy */
10731 NULL, /* remove_buddies */
10732 sipe_add_permit, /* add_permit */
10733 sipe_add_deny, /* add_deny */
10734 sipe_add_deny, /* rem_permit */
10735 sipe_add_permit, /* rem_deny */
10736 dummy_permit_deny, /* set_permit_deny */
10737 NULL, /* join_chat */
10738 NULL, /* reject_chat */
10739 NULL, /* get_chat_name */
10740 sipe_chat_invite, /* chat_invite */
10741 sipe_chat_leave, /* chat_leave */
10742 NULL, /* chat_whisper */
10743 sipe_chat_send, /* chat_send */
10744 sipe_keep_alive, /* keepalive */
10745 NULL, /* register_user */
10746 NULL, /* get_cb_info */ // deprecated
10747 NULL, /* get_cb_away */ // deprecated
10748 sipe_alias_buddy, /* alias_buddy */
10749 sipe_group_buddy, /* group_buddy */
10750 sipe_rename_group, /* rename_group */
10751 NULL, /* buddy_free */
10752 sipe_convo_closed, /* convo_closed */
10753 purple_normalize_nocase, /* normalize */
10754 NULL, /* set_buddy_icon */
10755 sipe_remove_group, /* remove_group */
10756 NULL, /* get_cb_real_name */ // TODO?
10757 NULL, /* set_chat_topic */
10758 NULL, /* find_blist_chat */
10759 NULL, /* roomlist_get_list */
10760 NULL, /* roomlist_cancel */
10761 NULL, /* roomlist_expand_category */
10762 NULL, /* can_receive_file */
10763 sipe_ft_send_file, /* send_file */
10764 sipe_ft_new_xfer, /* new_xfer */
10765 NULL, /* offline_message */
10766 NULL, /* whiteboard_prpl_ops */
10767 sipe_send_raw, /* send_raw */
10768 NULL, /* roomlist_room_serialize */
10769 NULL, /* unregister_user */
10770 NULL, /* send_attention */
10771 NULL, /* get_attention_types */
10772 #if !PURPLE_VERSION_CHECK(2,5,0)
10773 /* Backward compatibility when compiling against 2.4.x API */
10774 (void (*)(void)) /* _purple_reserved4 */
10775 #endif
10776 sizeof(PurplePluginProtocolInfo), /* struct_size */
10777 #if PURPLE_VERSION_CHECK(2,5,0)
10778 sipe_get_account_text_table, /* get_account_text_table */
10779 #if PURPLE_VERSION_CHECK(2,6,0)
10780 NULL, /* initiate_media */
10781 NULL, /* get_media_caps */
10782 #if PURPLE_VERSION_CHECK(2,7,0)
10783 NULL, /* get_moods */
10784 #endif
10785 #endif
10786 #endif
10790 PurplePluginInfo info = {
10791 PURPLE_PLUGIN_MAGIC,
10792 PURPLE_MAJOR_VERSION,
10793 PURPLE_MINOR_VERSION,
10794 PURPLE_PLUGIN_PROTOCOL, /**< type */
10795 NULL, /**< ui_requirement */
10796 0, /**< flags */
10797 NULL, /**< dependencies */
10798 PURPLE_PRIORITY_DEFAULT, /**< priority */
10799 "prpl-sipe", /**< id */
10800 "Office Communicator", /**< name */
10801 PACKAGE_VERSION, /**< version */
10802 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10803 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10804 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10805 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10806 "Gabriel Burt <gburt@novell.com>, " /**< author */
10807 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10808 "pier11 <pier11@operamail.com>", /**< author */
10809 PACKAGE_URL, /**< homepage */
10810 sipe_plugin_load, /**< load */
10811 sipe_plugin_unload, /**< unload */
10812 sipe_plugin_destroy, /**< destroy */
10813 NULL, /**< ui_info */
10814 &prpl_info, /**< extra_info */
10815 NULL,
10816 sipe_actions,
10817 NULL,
10818 NULL,
10819 NULL,
10820 NULL
10823 void sipe_core_init(void)
10825 srand(time(NULL));
10826 sip_sec_init();
10828 #ifdef ENABLE_NLS
10829 SIPE_DEBUG_INFO("bindtextdomain = %s",
10830 bindtextdomain(PACKAGE_NAME, LOCALEDIR));
10831 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10832 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"));
10833 textdomain(PACKAGE_NAME);
10834 #endif
10835 #ifdef HAVE_GMIME
10836 g_mime_init(0);
10837 #endif
10840 void sipe_core_destroy(void)
10842 #ifdef HAVE_GMIME
10843 g_mime_shutdown();
10844 #endif
10845 sip_sec_destroy();
10849 Local Variables:
10850 mode: c
10851 c-file-style: "bsd"
10852 indent-tabs-mode: t
10853 tab-width: 8
10854 End: