core cleanup: refactor SIP transport API to be the same as for HTTP
[siplcs.git] / src / core / sipe.c
blob801c3502cec657dbe92b4011f69adc3ecad00f88
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #ifdef _WIN32
41 #ifdef _DLL
42 #define _WS2TCPIP_H_
43 #define _WINSOCK2API_
44 #define _LIBC_INTERNAL_
45 #endif /* _DLL */
46 /* for network */
47 #include "libc_interface.h"
48 #else
49 #include <sys/types.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdlib.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <string.h>
59 #include <unistd.h>
61 #include <glib.h>
63 #include "sipe-common.h"
65 #include "account.h"
66 #include "blist.h"
67 #include "connection.h"
68 #include "conversation.h"
69 #include "core.h"
70 #include "circbuffer.h"
71 #include "dnsquery.h"
72 #include "dnssrv.h"
73 #include "ft.h"
74 #include "network.h"
75 #include "notify.h"
76 #include "plugin.h"
77 #include "privacy.h"
78 #include "request.h"
79 #include "savedstatuses.h"
80 #include "sslconn.h"
82 #include "core-depurple.h" /* Temporary for the core de-purple transition */
84 #include "http-conn.h"
85 #include "sipmsg.h"
86 #include "sip-csta.h"
87 #include "sip-sec.h"
88 #include "sipe-backend.h"
89 #include "sipe-buddy.h"
90 #include "sipe-cal.h"
91 #include "sipe-chat.h"
92 #include "sipe-conf.h"
93 #include "sipe-core.h"
94 #include "sipe-core-private.h"
95 #include "sipe-dialog.h"
96 #include "sipe-digest.h"
97 #include "sipe-ews.h"
98 #include "sipe-domino.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 "uuid.h"
107 #include "sipe.h"
109 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
111 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
112 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
114 /* Keep in sync with sipe_transport_type! */
115 static const char *transport_descriptor[] = { "", "tls", "tcp"};
116 #define TRANSPORT_DESCRIPTOR (transport_descriptor[SIP_TO_CORE_PRIVATE->transport_type])
118 /* Status identifiers (see also: sipe_status_types()) */
119 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
120 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
121 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
122 /* PURPLE_STATUS_UNAVAILABLE: */
123 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
124 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
125 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
126 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
127 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
128 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
129 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
130 /* PURPLE_STATUS_AWAY: */
131 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
132 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
133 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
134 /** Reuters status (user settable) */
135 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
136 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
137 /* ??? PURPLE_STATUS_MOBILE */
138 /* ??? PURPLE_STATUS_TUNE */
140 /* Status attributes (see also sipe_status_types() */
141 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
143 #ifdef HAVE_GMIME
144 /* pls. don't add multipart/related - it's not used in IM modality */
145 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
146 #else
147 /* this is a rediculous hack as Pidgin's MIME implementastion doesn't support (or have bug) in multipart/alternative */
148 /* OCS/OC won't use multipart/related so we don't advertase it */
149 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
150 #endif
152 static struct sipe_activity_map_struct
154 sipe_activity type;
155 const char *token;
156 const char *desc;
157 const char *status_id;
159 } const sipe_activity_map[] =
161 /* This has nothing to do with Availability numbers, like 3500 (online).
162 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
164 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
165 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
166 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
167 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
168 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
169 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
170 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
171 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
172 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
173 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
174 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
175 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
176 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
177 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
178 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
180 /** @param x is sipe_activity */
181 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
184 /* Action name templates */
185 #define ACTION_NAME_PRESENCE "<presence><%s>"
187 static sipe_activity
188 sipe_get_activity_by_token(const char *token)
190 int i;
192 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
194 if (sipe_strequal(token, sipe_activity_map[i].token))
195 return sipe_activity_map[i].type;
198 return sipe_activity_map[0].type;
201 static const char *
202 sipe_get_activity_desc_by_token(const char *token)
204 if (!token) return NULL;
206 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
209 /** Allows to send typed messages from chat window again after account reinstantiation. */
210 static void
211 sipe_rejoin_chat(PurpleConversation *conv)
213 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
214 PURPLE_CONV_CHAT(conv)->left)
216 PURPLE_CONV_CHAT(conv)->left = FALSE;
217 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
221 static char *genbranch()
223 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
224 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
225 rand() & 0xFFFF, rand() & 0xFFFF);
229 static char *default_ua = NULL;
230 static const char*
231 sipe_get_useragent(struct sipe_account_data *sip)
233 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
234 if (is_empty(useragent)) {
235 if (!default_ua) {
236 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
237 /* ref: lzodefs.h */
238 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
239 #define SIPE_TARGET_PLATFORM "linux"
240 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
241 #define SIPE_TARGET_PLATFORM "bsd"
242 #elif defined(__APPLE__) || defined(__MACOS__)
243 #define SIPE_TARGET_PLATFORM "macosx"
244 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
245 #define SIPE_TARGET_PLATFORM "aix"
246 #elif defined(__solaris__) || defined(__sun)
247 #define SIPE_TARGET_PLATFORM "sun"
248 #elif defined(_WIN32)
249 #define SIPE_TARGET_PLATFORM "win"
250 #elif defined(__CYGWIN__)
251 #define SIPE_TARGET_PLATFORM "cygwin"
252 #elif defined(__hpux__)
253 #define SIPE_TARGET_PLATFORM "hpux"
254 #elif defined(__sgi__)
255 #define SIPE_TARGET_PLATFORM "irix"
256 #else
257 #define SIPE_TARGET_PLATFORM "unknown"
258 #endif
260 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
261 #define SIPE_TARGET_ARCH "x86_64"
262 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
263 #define SIPE_TARGET_ARCH "i386"
264 #elif defined(__ppc64__)
265 #define SIPE_TARGET_ARCH "ppc64"
266 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
267 #define SIPE_TARGET_ARCH "ppc"
268 #elif defined(__hppa__) || defined(__hppa)
269 #define SIPE_TARGET_ARCH "hppa"
270 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
271 #define SIPE_TARGET_ARCH "mips"
272 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
273 #define SIPE_TARGET_ARCH "s390"
274 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
275 #define SIPE_TARGET_ARCH "sparc"
276 #elif defined(__arm__)
277 #define SIPE_TARGET_ARCH "arm"
278 #else
279 #define SIPE_TARGET_ARCH "other"
280 #endif
282 default_ua = g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION " (" SIPE_TARGET_PLATFORM "-" SIPE_TARGET_ARCH "; %s)",
283 purple_core_get_version(),
284 sip->server_version ? sip->server_version : "");
286 useragent = default_ua;
288 return useragent;
291 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
293 static void send_presence_status(struct sipe_core_private *sipe_private,
294 void *unused);
296 static void sipe_auth_free(struct sip_auth *auth)
298 g_free(auth->opaque);
299 auth->opaque = NULL;
300 g_free(auth->realm);
301 auth->realm = NULL;
302 g_free(auth->target);
303 auth->target = NULL;
304 auth->version = 0;
305 auth->type = AUTH_TYPE_UNSET;
306 auth->retries = 0;
307 auth->expires = 0;
308 g_free(auth->gssapi_data);
309 auth->gssapi_data = NULL;
310 sip_sec_destroy_context(auth->gssapi_context);
311 auth->gssapi_context = NULL;
314 static void
315 sipe_make_signature(struct sipe_account_data *sip,
316 struct sipmsg *msg);
318 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
320 const char *authuser = sip->authuser;
321 gchar *ret;
323 if (!authuser || strlen(authuser) < 1) {
324 authuser = sip->username;
327 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
328 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
329 gchar *version_str;
331 // If we have a signature for the message, include that
332 if (msg->signature) {
333 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);
336 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
337 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
338 gchar *gssapi_data;
339 gchar *opaque;
340 gchar *sign_str = NULL;
342 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
343 &(auth->expires),
344 auth->type,
345 purple_account_get_bool(sip->account, "sso", TRUE),
346 sip->authdomain ? sip->authdomain : "",
347 authuser,
348 sip->password,
349 auth->target,
350 auth->gssapi_data);
351 if (!gssapi_data || !auth->gssapi_context) {
352 purple_connection_error_reason(sip->gc,
353 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
354 _("Failed to authenticate to server"));
355 return NULL;
358 if (auth->version > 3) {
359 sipe_make_signature(sip, msg);
360 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
361 msg->rand, msg->num, msg->signature);
362 } else {
363 sign_str = g_strdup("");
366 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
367 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
368 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);
369 g_free(opaque);
370 g_free(gssapi_data);
371 g_free(version_str);
372 g_free(sign_str);
373 return ret;
376 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
377 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
378 g_free(version_str);
379 return ret;
381 } else { /* Digest */
382 gchar *string;
383 gchar *hex_digest;
384 guchar digest[SIPE_DIGEST_MD5_LENGTH];
386 /* Calculate new session key */
387 if (!auth->opaque) {
388 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
389 if (sip->password) {
391 * Calculate a session key for HTTP MD5 Digest authentation
393 * See RFC 2617 for more information.
395 string = g_strdup_printf("%s:%s:%s",
396 authuser,
397 auth->realm,
398 sip->password);
399 sipe_digest_md5((guchar *)string, strlen(string), digest);
400 g_free(string);
401 auth->opaque = buff_to_hex_str(digest, sizeof(digest));
406 * Calculate a response for HTTP MD5 Digest authentication
408 * See RFC 2617 for more information.
410 string = g_strdup_printf("%s:%s", msg->method, msg->target);
411 sipe_digest_md5((guchar *)string, strlen(string), digest);
412 g_free(string);
414 hex_digest = buff_to_hex_str(digest, sizeof(digest));
415 string = g_strdup_printf("%s:%s:%s", auth->opaque, auth->gssapi_data, hex_digest);
416 g_free(hex_digest);
417 sipe_digest_md5((guchar *)string, strlen(string), digest);
418 g_free(string);
420 hex_digest = buff_to_hex_str(digest, sizeof(digest));
421 SIPE_DEBUG_INFO("Digest response %s", hex_digest);
422 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);
423 g_free(hex_digest);
424 return ret;
428 static char *parse_attribute(const char *attrname, const char *source)
430 const char *tmp, *tmp2;
431 char *retval = NULL;
432 int len = strlen(attrname);
434 if (g_str_has_prefix(source, attrname)) {
435 tmp = source + len;
436 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
437 if (tmp2)
438 retval = g_strndup(tmp, tmp2 - tmp);
439 else
440 retval = g_strdup(tmp);
443 return retval;
446 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
448 int i;
449 gchar **parts;
451 if (!hdr) {
452 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
453 return;
456 if (!g_strncasecmp(hdr, "NTLM", 4)) {
457 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
458 auth->type = AUTH_TYPE_NTLM;
459 hdr += 5;
460 auth->nc = 1;
461 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
462 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
463 auth->type = AUTH_TYPE_KERBEROS;
464 hdr += 9;
465 auth->nc = 3;
466 } else {
467 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
468 auth->type = AUTH_TYPE_DIGEST;
469 hdr += 7;
472 parts = g_strsplit(hdr, "\", ", 0);
473 for (i = 0; parts[i]; i++) {
474 char *tmp;
476 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
478 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
479 g_free(auth->gssapi_data);
480 auth->gssapi_data = tmp;
482 if (auth->type == AUTH_TYPE_NTLM) {
483 /* NTLM module extracts nonce from gssapi-data */
484 auth->nc = 3;
487 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
488 /* Only used with AUTH_TYPE_DIGEST */
489 g_free(auth->gssapi_data);
490 auth->gssapi_data = tmp;
491 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
492 g_free(auth->opaque);
493 auth->opaque = tmp;
494 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
495 g_free(auth->realm);
496 auth->realm = tmp;
498 if (auth->type == AUTH_TYPE_DIGEST) {
499 /* Throw away old session key */
500 g_free(auth->opaque);
501 auth->opaque = NULL;
502 auth->nc = 1;
504 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
505 g_free(auth->target);
506 auth->target = tmp;
507 } else if ((tmp = parse_attribute("version=", parts[i]))) {
508 auth->version = atoi(tmp);
509 g_free(tmp);
511 // uncomment to revert to previous functionality if version 3+ does not work.
512 // auth->version = 2;
514 g_strfreev(parts);
516 return;
519 static void
520 sipe_make_signature(struct sipe_account_data *sip,
521 struct sipmsg *msg)
523 if (sip->registrar.gssapi_context) {
524 struct sipmsg_breakdown msgbd;
525 gchar *signature_input_str;
526 msgbd.msg = msg;
527 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
528 msgbd.rand = g_strdup_printf("%08x", g_random_int());
529 sip->registrar.ntlm_num++;
530 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
531 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
532 if (signature_input_str != NULL) {
533 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
534 msg->signature = signature_hex;
535 msg->rand = g_strdup(msgbd.rand);
536 msg->num = g_strdup(msgbd.num);
537 g_free(signature_input_str);
539 sipmsg_breakdown_free(&msgbd);
543 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
545 gchar * buf;
547 if (sip->registrar.type == AUTH_TYPE_UNSET) {
548 return;
551 sipe_make_signature(sip, msg);
553 if (sip->registrar.type && sipe_strequal(method, "REGISTER")) {
554 buf = auth_header(sip, &sip->registrar, msg);
555 if (buf) {
556 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
558 g_free(buf);
559 } 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")) {
560 sip->registrar.nc = 3;
561 sip->registrar.type = AUTH_TYPE_NTLM;
562 #ifdef HAVE_LIBKRB5
563 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
564 sip->registrar.type = AUTH_TYPE_KERBEROS;
566 #endif
569 buf = auth_header(sip, &sip->registrar, msg);
570 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
571 g_free(buf);
572 } else {
573 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method);
577 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
578 const char *text, const char *body)
580 gchar *name;
581 gchar *value;
582 GString *outstr = g_string_new("");
583 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
584 gchar *contact;
585 GSList *tmp;
586 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
588 /* Can return NULL! */
589 contact = get_contact(sip);
590 if (contact) {
591 sipmsg_add_header(msg, "Contact", contact);
592 g_free(contact);
595 if (body) {
596 gchar *len = g_strdup_printf("%" G_GSIZE_FORMAT , (gsize) strlen(body));
597 sipmsg_add_header(msg, "Content-Length", len);
598 g_free(len);
599 } else {
600 sipmsg_add_header(msg, "Content-Length", "0");
603 msg->response = code;
605 sipmsg_strip_headers(msg, keepers);
606 sipmsg_merge_new_headers(msg);
607 sign_outgoing_message(msg, sip, msg->method);
609 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
610 tmp = msg->headers;
611 while (tmp) {
612 name = ((struct sipnameval*) (tmp->data))->name;
613 value = ((struct sipnameval*) (tmp->data))->value;
615 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
616 tmp = g_slist_next(tmp);
618 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
619 sipe_backend_transport_sip_message(sip->public->transport, outstr->str);
620 g_string_free(outstr, TRUE);
623 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
625 if (sip->transactions) {
626 sip->transactions = g_slist_remove(sip->transactions, trans);
627 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip->transactions));
629 if (trans->msg) sipmsg_free(trans->msg);
630 if (trans->payload) {
631 (*trans->payload->destroy)(trans->payload->data);
632 g_free(trans->payload);
634 g_free(trans->key);
635 g_free(trans);
639 static struct transaction *
640 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
642 const gchar *call_id;
643 const gchar *cseq;
644 struct transaction *trans = g_new0(struct transaction, 1);
646 trans->time = time(NULL);
647 trans->msg = (struct sipmsg *)msg;
648 call_id = sipmsg_find_header(trans->msg, "Call-ID");
649 cseq = sipmsg_find_header(trans->msg, "CSeq");
650 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
651 trans->callback = callback;
652 sip->transactions = g_slist_append(sip->transactions, trans);
653 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip->transactions));
654 return trans;
657 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
659 struct transaction *trans;
660 GSList *transactions = sip->transactions;
661 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
662 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
663 gchar *key;
665 if (!call_id || !cseq) {
666 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
667 return NULL;
670 key = g_strdup_printf("<%s><%s>", call_id, cseq);
671 while (transactions) {
672 trans = transactions->data;
673 if (!g_strcasecmp(trans->key, key)) {
674 g_free(key);
675 return trans;
677 transactions = transactions->next;
680 g_free(key);
681 return NULL;
684 struct transaction *
685 send_sip_request(PurpleConnection *gc, const gchar *method,
686 const gchar *url, const gchar *to, const gchar *addheaders,
687 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
689 struct sipe_core_private *sipe_private = gc->proto_data;
690 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
691 char *buf;
692 struct sipmsg *msg;
693 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
694 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
695 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
696 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
697 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
698 gchar *route = g_strdup("");
699 gchar *epid = get_epid(sip);
700 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
701 struct transaction *trans = NULL;
703 if (dialog && dialog->routes)
705 GSList *iter = dialog->routes;
707 while(iter)
709 char *tmp = route;
710 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
711 g_free(tmp);
712 iter = g_slist_next(iter);
716 if (!ourtag && !dialog) {
717 ourtag = gentag();
720 if (sipe_strequal(method, "REGISTER")) {
721 if (sip->regcallid) {
722 g_free(callid);
723 callid = g_strdup(sip->regcallid);
724 } else {
725 sip->regcallid = g_strdup(callid);
727 cseq = ++sip->cseq;
730 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
731 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
732 "From: <sip:%s>%s%s;epid=%s\r\n"
733 "To: <%s>%s%s%s%s\r\n"
734 "Max-Forwards: 70\r\n"
735 "CSeq: %d %s\r\n"
736 "User-Agent: %s\r\n"
737 "Call-ID: %s\r\n"
738 "%s%s"
739 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
740 method,
741 dialog && dialog->request ? dialog->request : url,
742 TRANSPORT_DESCRIPTOR,
743 sipe_backend_network_ip_address(),
744 sipe_private->public.transport->client_port,
745 branch ? ";branch=" : "",
746 branch ? branch : "",
747 sip->username,
748 ourtag ? ";tag=" : "",
749 ourtag ? ourtag : "",
750 epid,
752 theirtag ? ";tag=" : "",
753 theirtag ? theirtag : "",
754 theirepid ? ";epid=" : "",
755 theirepid ? theirepid : "",
756 cseq,
757 method,
758 sipe_get_useragent(sip),
759 callid,
760 route,
761 addheaders ? addheaders : "",
762 body ? (gsize) strlen(body) : 0,
763 body ? body : "");
766 //printf ("parsing msg buf:\n%s\n\n", buf);
767 msg = sipmsg_parse_msg(buf);
769 g_free(buf);
770 g_free(ourtag);
771 g_free(theirtag);
772 g_free(theirepid);
773 g_free(branch);
774 g_free(callid);
775 g_free(route);
776 g_free(epid);
778 sign_outgoing_message (msg, sip, method);
780 buf = sipmsg_to_string (msg);
782 /* add to ongoing transactions */
783 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
784 if (!sipe_strequal(method, "ACK")) {
785 trans = transactions_add_buf(sip, msg, tc);
786 } else {
787 sipmsg_free(msg);
789 sipe_backend_transport_sip_message(sipe_private->public.transport, buf);
790 g_free(buf);
792 return trans;
796 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
798 static void
799 send_soap_request_with_cb(struct sipe_account_data *sip,
800 gchar *from0,
801 gchar *body,
802 TransCallback callback,
803 struct transaction_payload *payload)
805 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
806 gchar *contact = get_contact(sip);
807 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
808 "Content-Type: application/SOAP+xml\r\n",contact);
810 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
811 trans->payload = payload;
813 g_free(from);
814 g_free(contact);
815 g_free(hdr);
818 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
820 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
823 static void do_register_exp(struct sipe_account_data *sip, int expire)
825 char *uri;
826 char *expires;
827 char *to;
828 char *hdr;
829 char *epid;
830 char *uuid;
832 if (!SIP_TO_CORE_PUBLIC->sip_domain) return;
834 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
835 epid = get_epid(sip);
836 uuid = generateUUIDfromEPID(epid);
837 hdr = g_strdup_printf("Contact: <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>\"\r\n"
838 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
839 "Event: registration\r\n"
840 "Allow-Events: presence\r\n"
841 "ms-keep-alive: UAC;hop-hop=yes\r\n"
842 "%s",
843 sipe_backend_network_ip_address(),
844 SIP_TO_CORE_PUBLIC->transport->client_port,
845 TRANSPORT_DESCRIPTOR,
846 uuid,
847 expires);
848 g_free(uuid);
849 g_free(epid);
850 g_free(expires);
852 sip->registerstatus = 1;
854 uri = sip_uri_from_name(SIP_TO_CORE_PUBLIC->sip_domain);
855 to = sip_uri_self(sip);
856 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
857 process_register_response);
858 g_free(to);
859 g_free(uri);
860 g_free(hdr);
863 static void do_register_cb(struct sipe_core_private *sipe_private,
864 SIPE_UNUSED_PARAMETER void *unused)
866 struct sipe_account_data *sip = sipe_private->temporary;
867 do_register_exp(sip, -1);
868 sip->reregister_set = FALSE;
871 static void do_register(struct sipe_account_data *sip)
873 do_register_exp(sip, -1);
877 * Returns pointer to URI without sip: prefix if any
879 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
880 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
882 * Doesn't allocate memory
884 static const char *
885 sipe_get_no_sip_uri(const char *sip_uri)
887 const char *prefix = "sip:";
888 if (!sip_uri) return NULL;
890 if (g_str_has_prefix(sip_uri, prefix)) {
891 return (sip_uri+strlen(prefix));
892 } else {
893 return sip_uri;
897 static void
898 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
900 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
901 send_soap_request(sip, body);
902 g_free(body);
905 static void
906 sipe_change_access_level(struct sipe_account_data *sip,
907 const int container_id,
908 const gchar *type,
909 const gchar *value);
911 void
912 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
913 const gchar * who, gboolean allow)
915 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
916 if (allow) {
917 SIPE_DEBUG_INFO("Authorizing contact %s", who);
918 } else {
919 SIPE_DEBUG_INFO("Blocking contact %s", who);
922 if (sip->ocs2007) {
923 sipe_change_access_level(sip, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
924 } else {
925 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
929 static
930 void sipe_auth_user_cb(void * data)
932 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
933 if (!job) return;
935 sipe_core_contact_allow_deny(job->sip->public, job->who, TRUE);
936 g_free(job);
939 static
940 void sipe_deny_user_cb(void * data)
942 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
943 if (!job) return;
945 sipe_core_contact_allow_deny(job->sip->public, job->who, FALSE);
946 g_free(job);
949 /** @applicable: 2005-
951 static void
952 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
954 sipe_xml *watchers;
955 const sipe_xml *watcher;
956 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
957 if (msg->response != 0 && msg->response != 200) return;
959 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
961 watchers = sipe_xml_parse(msg->body, msg->bodylen);
962 if (!watchers) return;
964 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
965 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
966 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
967 gboolean on_list = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, remote_user) != NULL;
969 // TODO pull out optional displayName to pass as alias
970 if (remote_user) {
971 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
972 job->who = remote_user;
973 job->sip = sip;
974 purple_account_request_authorization(
975 sip->account,
976 remote_user,
977 _("you"), /* id */
978 alias,
979 NULL, /* message */
980 on_list,
981 sipe_auth_user_cb,
982 sipe_deny_user_cb,
983 (void *) job);
988 sipe_xml_free(watchers);
989 return;
992 static void
993 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
995 PurpleGroup * purple_group = purple_find_group(group->name);
996 if (!purple_group) {
997 purple_group = purple_group_new(group->name);
998 purple_blist_add_group(purple_group, NULL);
1001 if (purple_group) {
1002 group->purple_group = purple_group;
1003 sip->groups = g_slist_append(sip->groups, group);
1004 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
1005 } else {
1006 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
1010 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1012 struct sipe_group *group;
1013 GSList *entry;
1014 if (sip == NULL) {
1015 return NULL;
1018 entry = sip->groups;
1019 while (entry) {
1020 group = entry->data;
1021 if (group->id == id) {
1022 return group;
1024 entry = entry->next;
1026 return NULL;
1029 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1031 struct sipe_group *group;
1032 GSList *entry;
1033 if (!sip || !name) {
1034 return NULL;
1037 entry = sip->groups;
1038 while (entry) {
1039 group = entry->data;
1040 if (sipe_strequal(group->name, name)) {
1041 return group;
1043 entry = entry->next;
1045 return NULL;
1048 static void
1049 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1051 gchar *body;
1052 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
1053 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1054 send_soap_request(sip, body);
1055 g_free(body);
1056 g_free(group->name);
1057 group->name = g_strdup(name);
1061 * Only appends if no such value already stored.
1062 * Like Set in Java.
1064 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1065 GSList * res = list;
1066 if (!g_slist_find_custom(list, data, func)) {
1067 res = g_slist_insert_sorted(list, data, func);
1069 return res;
1072 static int
1073 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1074 return group1->id - group2->id;
1078 * Returns string like "2 4 7 8" - group ids buddy belong to.
1080 static gchar *
1081 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1082 int i = 0;
1083 gchar *res;
1084 //creating array from GList, converting int to gchar*
1085 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1086 GSList *entry = buddy->groups;
1088 if (!ids_arr) return NULL;
1090 while (entry) {
1091 struct sipe_group * group = entry->data;
1092 ids_arr[i] = g_strdup_printf("%d", group->id);
1093 entry = entry->next;
1094 i++;
1096 ids_arr[i] = NULL;
1097 res = g_strjoinv(" ", ids_arr);
1098 g_strfreev(ids_arr);
1099 return res;
1103 * Sends buddy update to server
1105 void
1106 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
1108 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
1109 struct sipe_buddy *buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, who);
1110 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1112 if (buddy && purple_buddy) {
1113 const char *alias = purple_buddy_get_alias(purple_buddy);
1114 gchar *groups = sipe_get_buddy_groups_string(buddy);
1115 if (groups) {
1116 gchar *body;
1117 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
1119 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1120 alias, groups, "true", buddy->name, sip->contacts_delta++
1122 send_soap_request(sip, body);
1123 g_free(groups);
1124 g_free(body);
1129 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1131 if (msg->response == 200) {
1132 struct sipe_group *group;
1133 struct group_user_context *ctx = trans->payload->data;
1134 sipe_xml *xml;
1135 const sipe_xml *node;
1136 char *group_id;
1137 struct sipe_buddy *buddy;
1139 xml = sipe_xml_parse(msg->body, msg->bodylen);
1140 if (!xml) {
1141 return FALSE;
1144 node = sipe_xml_child(xml, "Body/addGroup/groupID");
1145 if (!node) {
1146 sipe_xml_free(xml);
1147 return FALSE;
1150 group_id = sipe_xml_data(node);
1151 if (!group_id) {
1152 sipe_xml_free(xml);
1153 return FALSE;
1156 group = g_new0(struct sipe_group, 1);
1157 group->id = (int)g_ascii_strtod(group_id, NULL);
1158 g_free(group_id);
1159 group->name = g_strdup(ctx->group_name);
1161 sipe_group_add(sip, group);
1163 buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, ctx->user_name);
1164 if (buddy) {
1165 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1168 sipe_core_group_set_user(SIP_TO_CORE_PUBLIC, ctx->user_name);
1170 sipe_xml_free(xml);
1171 return TRUE;
1173 return FALSE;
1176 static void sipe_group_context_destroy(gpointer data)
1178 struct group_user_context *ctx = data;
1179 g_free(ctx->group_name);
1180 g_free(ctx->user_name);
1181 g_free(ctx);
1184 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1186 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1187 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1188 gchar *body;
1189 ctx->group_name = g_strdup(name);
1190 ctx->user_name = g_strdup(who);
1191 payload->destroy = sipe_group_context_destroy;
1192 payload->data = ctx;
1194 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1195 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1196 g_free(body);
1200 * Data structure for scheduled actions
1203 struct scheduled_action {
1205 * Name of action.
1206 * Format is <Event>[<Data>...]
1207 * Example: <presence><sip:user@domain.com> or <registration>
1209 gchar *name;
1210 guint timeout_handler;
1211 gboolean repetitive;
1212 Action action;
1213 GDestroyNotify destroy;
1214 struct sipe_core_private *sipe_private;
1215 void *payload;
1219 * A timer callback
1220 * Should return FALSE if repetitive action is not needed
1222 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1224 gboolean ret;
1225 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1226 sched_action->sipe_private->timeouts = g_slist_remove(sched_action->sipe_private->timeouts, sched_action);
1227 SIPE_DEBUG_INFO("sipe_private->timeouts count:%d after removal", g_slist_length(sched_action->sipe_private->timeouts));
1228 (sched_action->action)(sched_action->sipe_private, sched_action->payload);
1229 ret = sched_action->repetitive;
1230 if (sched_action->destroy) {
1231 (*sched_action->destroy)(sched_action->payload);
1233 g_free(sched_action->name);
1234 g_free(sched_action);
1235 return ret;
1239 * Kills action timer effectively cancelling
1240 * scheduled action
1242 * @param name of action
1244 static void sipe_cancel_scheduled_action(struct sipe_core_private *sipe_private,
1245 const gchar *name)
1247 GSList *entry;
1249 if (!sipe_private->timeouts || !name) return;
1251 entry = sipe_private->timeouts;
1252 while (entry) {
1253 struct scheduled_action *sched_action = entry->data;
1254 if(sipe_strequal(sched_action->name, name)) {
1255 GSList *to_delete = entry;
1256 entry = entry->next;
1257 sipe_private->timeouts = g_slist_delete_link(sipe_private->timeouts, to_delete);
1258 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
1259 purple_timeout_remove(sched_action->timeout_handler);
1260 if (sched_action->destroy) {
1261 (*sched_action->destroy)(sched_action->payload);
1263 g_free(sched_action->name);
1264 g_free(sched_action);
1265 } else {
1266 entry = entry->next;
1271 static void
1272 sipe_schedule_action0(const gchar *name,
1273 int timeout,
1274 gboolean isSeconds,
1275 Action action,
1276 GDestroyNotify destroy,
1277 struct sipe_core_private *sipe_private,
1278 void *payload)
1280 struct scheduled_action *sched_action;
1282 /* Make sure each action only exists once */
1283 sipe_cancel_scheduled_action(sipe_private, name);
1285 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name, timeout, isSeconds ? "sec" : "msec");
1286 sched_action = g_new0(struct scheduled_action, 1);
1287 sched_action->repetitive = FALSE;
1288 sched_action->name = g_strdup(name);
1289 sched_action->action = action;
1290 sched_action->destroy = destroy;
1291 sched_action->sipe_private = sipe_private;
1292 sched_action->payload = payload;
1293 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1294 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1295 sipe_private->timeouts = g_slist_append(sipe_private->timeouts, sched_action);
1296 SIPE_DEBUG_INFO("sipe_private->timeouts count:%d after addition", g_slist_length(sipe_private->timeouts));
1299 void
1300 sipe_schedule_action(const gchar *name,
1301 int timeout,
1302 Action action,
1303 GDestroyNotify destroy,
1304 struct sipe_core_private *sipe_private,
1305 void *payload)
1307 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sipe_private, payload);
1311 * Same as sipe_schedule_action() but timeout is in milliseconds.
1313 static void
1314 sipe_schedule_action_msec(const gchar *name,
1315 int timeout,
1316 Action action,
1317 GDestroyNotify destroy,
1318 struct sipe_core_private *sipe_private,
1319 void *payload)
1321 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sipe_private, payload);
1324 static void
1325 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1326 time_t calculate_from);
1328 static int
1329 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
1331 static const char*
1332 sipe_get_status_by_availability(int avail,
1333 char** activity);
1335 static void
1336 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1337 const char *status_id,
1338 const char *message,
1339 time_t do_not_publish[]);
1341 static void
1342 sipe_apply_calendar_status(struct sipe_account_data *sip,
1343 struct sipe_buddy *sbuddy,
1344 const char *status_id)
1346 time_t cal_avail_since;
1347 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
1348 int avail;
1349 gchar *self_uri;
1351 if (!sbuddy) return;
1353 if (cal_status < SIPE_CAL_NO_DATA) {
1354 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
1355 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
1358 /* scheduled Cal update call */
1359 if (!status_id) {
1360 status_id = sbuddy->last_non_cal_status_id;
1361 g_free(sbuddy->activity);
1362 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
1365 if (!status_id) {
1366 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1367 sbuddy->name ? sbuddy->name : "" );
1368 return;
1371 /* adjust to calendar status */
1372 if (cal_status != SIPE_CAL_NO_DATA) {
1373 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
1375 if (cal_status == SIPE_CAL_BUSY
1376 && cal_avail_since > sbuddy->user_avail_since
1377 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
1379 status_id = SIPE_STATUS_ID_BUSY;
1380 g_free(sbuddy->activity);
1381 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
1383 avail = sipe_get_availability_by_status(status_id, NULL);
1385 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
1386 if (cal_avail_since > sbuddy->activity_since) {
1387 if (cal_status == SIPE_CAL_OOF
1388 && avail >= 15000) /* 12000 in 2007 */
1390 g_free(sbuddy->activity);
1391 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
1396 /* then set status_id actually */
1397 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
1398 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
1400 /* set our account state to the one in roaming (including calendar info) */
1401 self_uri = sip_uri_self(sip);
1402 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
1403 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
1404 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
1407 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
1408 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
1410 g_free(self_uri);
1413 static void
1414 sipe_got_user_status(struct sipe_account_data *sip,
1415 const char* uri,
1416 const char *status_id)
1418 struct sipe_buddy *sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri);
1420 if (!sbuddy) return;
1422 /* Check if on 2005 system contact's calendar,
1423 * then set/preserve it.
1425 if (!sip->ocs2007) {
1426 sipe_apply_calendar_status(sip, sbuddy, status_id);
1427 } else {
1428 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
1432 static void
1433 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
1434 struct sipe_buddy *sbuddy,
1435 struct sipe_account_data *sip)
1437 sipe_apply_calendar_status(sip, sbuddy, NULL);
1441 * Updates contact's status
1442 * based on their calendar information.
1444 * Applicability: 2005 systems
1446 static void
1447 update_calendar_status(struct sipe_core_private *sipe_private,
1448 SIPE_UNUSED_PARAMETER void *unused)
1450 struct sipe_account_data *sip = sipe_private->temporary;
1452 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1453 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc)update_calendar_status_cb, (gpointer)sip);
1455 /* repeat scheduling */
1456 sipe_sched_calendar_status_update(sip, time(NULL) + 3*60 /* 3 min */);
1460 * Schedules process of contacts' status update
1461 * based on their calendar information.
1462 * Should be scheduled to the beginning of every
1463 * 15 min interval, like:
1464 * 13:00, 13:15, 13:30, 13:45, etc.
1466 * Applicability: 2005 systems
1468 static void
1469 sipe_sched_calendar_status_update(struct sipe_account_data *sip,
1470 time_t calculate_from)
1472 int interval = 15*60;
1473 /** start of the beginning of closest 15 min interval. */
1474 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1476 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1477 asctime(localtime(&calculate_from)));
1478 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1479 asctime(localtime(&next_start)));
1481 sipe_schedule_action("<+2005-cal-status>",
1482 (int)(next_start - time(NULL)),
1483 update_calendar_status,
1484 NULL,
1485 SIP_TO_CORE_PRIVATE,
1486 NULL);
1490 * Schedules process of self status publish
1491 * based on own calendar information.
1492 * Should be scheduled to the beginning of every
1493 * 15 min interval, like:
1494 * 13:00, 13:15, 13:30, 13:45, etc.
1496 * Applicability: 2007+ systems
1498 static void
1499 sipe_sched_calendar_status_self_publish(struct sipe_account_data *sip,
1500 time_t calculate_from)
1502 int interval = 5*60;
1503 /** start of the beginning of closest 5 min interval. */
1504 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1506 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1507 asctime(localtime(&calculate_from)));
1508 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1509 asctime(localtime(&next_start)));
1511 sipe_schedule_action("<+2007-cal-status>",
1512 (int)(next_start - time(NULL)),
1513 publish_calendar_status_self,
1514 NULL,
1515 SIP_TO_CORE_PRIVATE,
1516 NULL);
1519 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1521 /** Should be g_free()'d
1523 static gchar *
1524 sipe_get_subscription_key(const gchar *event,
1525 const gchar *with)
1527 gchar *key = NULL;
1529 if (is_empty(event)) return NULL;
1531 if (event && sipe_strcase_equal(event, "presence")) {
1532 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1533 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1535 /* @TODO drop participated buddies' just_added flag */
1536 } else if (event) {
1537 /* Subscription is identified by <event> key */
1538 key = g_strdup_printf("<%s>", event);
1541 return key;
1544 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1545 SIPE_UNUSED_PARAMETER struct transaction *trans)
1547 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1548 const gchar *event = sipmsg_find_header(msg, "Event");
1549 gchar *key;
1551 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1552 if (!event) {
1553 struct sipmsg *request_msg = trans->msg;
1554 event = sipmsg_find_header(request_msg, "Event");
1557 key = sipe_get_subscription_key(event, with);
1559 /* 200 OK; 481 Call Leg Does Not Exist */
1560 if (key && (msg->response == 200 || msg->response == 481)) {
1561 if (g_hash_table_lookup(sip->subscriptions, key)) {
1562 g_hash_table_remove(sip->subscriptions, key);
1563 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1567 /* create/store subscription dialog if not yet */
1568 if (msg->response == 200) {
1569 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1570 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1572 if (key) {
1573 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1574 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1576 subscription->dialog.callid = g_strdup(callid);
1577 subscription->dialog.cseq = atoi(cseq);
1578 subscription->dialog.with = g_strdup(with);
1579 subscription->event = g_strdup(event);
1580 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1582 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1585 g_free(cseq);
1588 g_free(key);
1589 g_free(with);
1591 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1593 process_incoming_notify(sip, msg, FALSE, FALSE);
1595 return TRUE;
1598 static void sipe_subscribe_resource_uri(const char *name,
1599 SIPE_UNUSED_PARAMETER gpointer value,
1600 gchar **resources_uri)
1602 gchar *tmp = *resources_uri;
1603 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1604 g_free(tmp);
1607 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1609 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1610 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1611 gchar *tmp = *resources_uri;
1613 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1615 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1616 g_free(tmp);
1620 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1621 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1622 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1623 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1624 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1627 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1629 gchar *key;
1630 gchar *contact = get_contact(sip);
1631 gchar *request;
1632 gchar *content;
1633 gchar *require = "";
1634 gchar *accept = "";
1635 gchar *autoextend = "";
1636 gchar *content_type;
1637 struct sip_dialog *dialog;
1639 if (sip->ocs2007) {
1640 require = ", categoryList";
1641 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1642 content_type = "application/msrtc-adrl-categorylist+xml";
1643 content = g_strdup_printf(
1644 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1645 "<action name=\"subscribe\" id=\"63792024\">\n"
1646 "<adhocList>\n%s</adhocList>\n"
1647 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1648 "<category name=\"calendarData\"/>\n"
1649 "<category name=\"contactCard\"/>\n"
1650 "<category name=\"note\"/>\n"
1651 "<category name=\"state\"/>\n"
1652 "</categoryList>\n"
1653 "</action>\n"
1654 "</batchSub>", sip->username, resources_uri);
1655 } else {
1656 autoextend = "Supported: com.microsoft.autoextend\r\n";
1657 content_type = "application/adrl+xml";
1658 content = g_strdup_printf(
1659 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1660 "<create xmlns=\"\">\n%s</create>\n"
1661 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1663 g_free(resources_uri);
1665 request = g_strdup_printf(
1666 "Require: adhoclist%s\r\n"
1667 "Supported: eventlist\r\n"
1668 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1669 "Supported: ms-piggyback-first-notify\r\n"
1670 "%sSupported: ms-benotify\r\n"
1671 "Proxy-Require: ms-benotify\r\n"
1672 "Event: presence\r\n"
1673 "Content-Type: %s\r\n"
1674 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1675 g_free(contact);
1677 /* subscribe to buddy presence */
1678 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1679 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1680 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1681 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
1683 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1685 g_free(content);
1686 g_free(to);
1687 g_free(request);
1688 g_free(key);
1691 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1692 SIPE_UNUSED_PARAMETER void *unused)
1694 gchar *to = sip_uri_self(sip);
1695 gchar *resources_uri = g_strdup("");
1696 if (sip->ocs2007) {
1697 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1698 } else {
1699 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1702 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1705 struct presence_batched_routed {
1706 gchar *host;
1707 GSList *buddies;
1710 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1712 struct presence_batched_routed *data = payload;
1713 GSList *buddies = data->buddies;
1714 while (buddies) {
1715 g_free(buddies->data);
1716 buddies = buddies->next;
1718 g_slist_free(data->buddies);
1719 g_free(data->host);
1720 g_free(payload);
1723 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
1724 void *payload)
1726 struct sipe_account_data *sip = sipe_private->temporary;
1727 struct presence_batched_routed *data = payload;
1728 GSList *buddies = data->buddies;
1729 gchar *resources_uri = g_strdup("");
1730 while (buddies) {
1731 gchar *tmp = resources_uri;
1732 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1733 g_free(tmp);
1734 buddies = buddies->next;
1736 sipe_subscribe_presence_batched_to(sip, resources_uri,
1737 g_strdup(data->host));
1741 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1742 * The user sends a single SUBSCRIBE request to the subscribed contact.
1743 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1747 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
1748 void *buddy_name)
1750 struct sipe_account_data *sip = sipe_private->temporary;
1751 gchar *key;
1752 gchar *to = sip_uri((char *)buddy_name);
1753 gchar *tmp = get_contact(sip);
1754 gchar *request;
1755 gchar *content = NULL;
1756 gchar *autoextend = "";
1757 gchar *content_type = "";
1758 struct sip_dialog *dialog;
1759 struct sipe_buddy *sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, to);
1760 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1762 if (sbuddy) sbuddy->just_added = FALSE;
1764 if (sip->ocs2007) {
1765 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1766 } else {
1767 autoextend = "Supported: com.microsoft.autoextend\r\n";
1770 request = g_strdup_printf(
1771 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1772 "Supported: ms-piggyback-first-notify\r\n"
1773 "%s%sSupported: ms-benotify\r\n"
1774 "Proxy-Require: ms-benotify\r\n"
1775 "Event: presence\r\n"
1776 "Contact: %s\r\n", autoextend, content_type, tmp);
1778 if (sip->ocs2007) {
1779 content = g_strdup_printf(
1780 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1781 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1782 "<resource uri=\"%s\"%s\n"
1783 "</adhocList>\n"
1784 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1785 "<category name=\"calendarData\"/>\n"
1786 "<category name=\"contactCard\"/>\n"
1787 "<category name=\"note\"/>\n"
1788 "<category name=\"state\"/>\n"
1789 "</categoryList>\n"
1790 "</action>\n"
1791 "</batchSub>", sip->username, to, context);
1794 g_free(tmp);
1796 /* subscribe to buddy presence */
1797 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1798 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1799 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1800 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
1802 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1804 g_free(content);
1805 g_free(to);
1806 g_free(request);
1807 g_free(key);
1810 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1812 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
1814 if (!purple_status_is_active(status))
1815 return;
1817 if (account->gc) {
1818 struct sipe_account_data *sip = PURPLE_ACCOUNT_TO_SIPE_ACCOUNT_DATA;
1820 if (sip) {
1821 gchar *action_name;
1822 gchar *tmp;
1823 time_t now = time(NULL);
1824 const char *status_id = purple_status_get_id(status);
1825 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
1826 sipe_activity activity = sipe_get_activity_by_token(status_id);
1827 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
1829 /* when other point of presence clears note, but we are keeping
1830 * state if OOF note.
1832 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
1833 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
1834 do_not_publish = FALSE;
1837 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
1838 status_id, (int)sip->do_not_publish[activity], (int)now);
1840 sip->do_not_publish[activity] = 0;
1841 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
1842 status_id, (int)sip->do_not_publish[activity]);
1844 if (do_not_publish)
1846 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
1847 return;
1850 g_free(sip->status);
1851 sip->status = g_strdup(status_id);
1853 /* hack to escape apostrof before comparison */
1854 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
1856 /* this will preserve OOF flag as well */
1857 if (!sipe_strequal(tmp, sip->note)) {
1858 sip->is_oof_note = FALSE;
1859 g_free(sip->note);
1860 sip->note = g_strdup(note);
1861 sip->note_since = time(NULL);
1863 g_free(tmp);
1865 /* schedule 2 sec to capture idle flag */
1866 action_name = g_strdup_printf("<%s>", "+set-status");
1867 sipe_schedule_action(action_name,
1868 SIPE_IDLE_SET_DELAY,
1869 send_presence_status,
1870 NULL,
1871 SIP_TO_CORE_PRIVATE,
1872 NULL);
1873 g_free(action_name);
1878 void
1879 sipe_set_idle(PurpleConnection * gc,
1880 int interval)
1882 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1884 if (gc) {
1885 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
1887 if (sip) {
1888 sip->idle_switch = time(NULL);
1889 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1894 void
1895 sipe_group_buddy(PurpleConnection *gc,
1896 const char *who,
1897 const char *old_group_name,
1898 const char *new_group_name)
1900 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
1901 struct sipe_buddy * buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, who);
1902 struct sipe_group * old_group = NULL;
1903 struct sipe_group * new_group;
1905 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1906 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1908 if(!buddy) { // buddy not in roaming list
1909 return;
1912 if (old_group_name) {
1913 old_group = sipe_group_find_by_name(sip, old_group_name);
1915 new_group = sipe_group_find_by_name(sip, new_group_name);
1917 if (old_group) {
1918 buddy->groups = g_slist_remove(buddy->groups, old_group);
1919 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1922 if (!new_group) {
1923 sipe_group_create(sip, new_group_name, who);
1924 } else {
1925 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1926 sipe_core_group_set_user(SIP_TO_CORE_PUBLIC, who);
1930 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1932 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1934 /* libpurple can call us with undefined buddy or group */
1935 if (buddy && group) {
1936 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
1938 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1939 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1940 purple_blist_rename_buddy(buddy, buddy_name);
1941 g_free(buddy_name);
1943 /* Prepend sip: if needed */
1944 if (!g_str_has_prefix(buddy->name, "sip:")) {
1945 gchar *buf = sip_uri_from_name(buddy->name);
1946 purple_blist_rename_buddy(buddy, buf);
1947 g_free(buf);
1950 if (!g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, buddy->name)) {
1951 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1952 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1953 b->name = g_strdup(buddy->name);
1954 b->just_added = TRUE;
1955 g_hash_table_insert(SIP_TO_CORE_PRIVATE->buddies, b->name, b);
1956 sipe_group_buddy(gc, b->name, NULL, group->name);
1957 /* @TODO should go to callback */
1958 sipe_subscribe_presence_single(SIP_TO_CORE_PRIVATE,
1959 b->name);
1960 } else {
1961 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
1966 static void sipe_free_buddy(struct sipe_buddy *buddy)
1968 #ifndef _WIN32
1970 * We are calling g_hash_table_foreach_steal(). That means that no
1971 * key/value deallocation functions are called. Therefore the glib
1972 * hash code does not touch the key (buddy->name) or value (buddy)
1973 * of the to-be-deleted hash node at all. It follows that we
1975 * - MUST free the memory for the key ourselves and
1976 * - ARE allowed to do it in this function
1978 * Conclusion: glib must be broken on the Windows platform if sipe
1979 * crashes with SIGTRAP when closing. You'll have to live
1980 * with the memory leak until this is fixed.
1982 g_free(buddy->name);
1983 #endif
1984 g_free(buddy->activity);
1985 g_free(buddy->meeting_subject);
1986 g_free(buddy->meeting_location);
1987 g_free(buddy->note);
1989 g_free(buddy->cal_start_time);
1990 g_free(buddy->cal_free_busy_base64);
1991 g_free(buddy->cal_free_busy);
1992 g_free(buddy->last_non_cal_activity);
1994 sipe_cal_free_working_hours(buddy->cal_working_hours);
1996 g_free(buddy->device_name);
1997 g_slist_free(buddy->groups);
1998 g_free(buddy);
2002 * Unassociates buddy from group first.
2003 * Then see if no groups left, removes buddy completely.
2004 * Otherwise updates buddy groups on server.
2006 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2008 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2009 struct sipe_buddy *b;
2010 struct sipe_group *g = NULL;
2012 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
2013 if (!buddy) return;
2015 b = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, buddy->name);
2016 if (!b) return;
2018 if (group) {
2019 g = sipe_group_find_by_name(sip, group->name);
2022 if (g) {
2023 b->groups = g_slist_remove(b->groups, g);
2024 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
2027 if (g_slist_length(b->groups) < 1) {
2028 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2029 sipe_cancel_scheduled_action(SIP_TO_CORE_PRIVATE, action_name);
2030 g_free(action_name);
2032 g_hash_table_remove(SIP_TO_CORE_PRIVATE->buddies, buddy->name);
2034 if (b->name) {
2035 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
2036 send_soap_request(sip, body);
2037 g_free(body);
2040 sipe_free_buddy(b);
2041 } else {
2042 //updates groups on server
2043 sipe_core_group_set_user(SIP_TO_CORE_PUBLIC, b->name);
2048 void
2049 sipe_rename_group(PurpleConnection *gc,
2050 const char *old_name,
2051 PurpleGroup *group,
2052 SIPE_UNUSED_PARAMETER GList *moved_buddies)
2054 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2055 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
2056 if (s_group) {
2057 sipe_group_rename(sip, s_group, group->name);
2058 } else {
2059 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
2063 void
2064 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
2066 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
2067 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
2068 if (s_group) {
2069 gchar *body;
2070 SIPE_DEBUG_INFO("Deleting group %s", group->name);
2071 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
2072 send_soap_request(sip, body);
2073 g_free(body);
2075 sip->groups = g_slist_remove(sip->groups, s_group);
2076 g_free(s_group->name);
2077 g_free(s_group);
2078 } else {
2079 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
2084 * A callback for g_hash_table_foreach
2086 static void
2087 sipe_buddy_subscribe_cb(char *buddy_name,
2088 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2089 struct sipe_account_data *sip)
2091 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
2092 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2093 guint time_range = (g_hash_table_size(SIP_TO_CORE_PRIVATE->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2094 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2096 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, SIP_TO_CORE_PRIVATE, g_strdup(buddy_name));
2097 g_free(action_name);
2101 * Removes entries from purple buddy list
2102 * that does not correspond ones in the roaming contact list.
2104 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2105 GSList *buddies = purple_find_buddies(sip->account, NULL);
2106 GSList *entry = buddies;
2107 struct sipe_buddy *buddy;
2108 PurpleBuddy *b;
2109 PurpleGroup *g;
2111 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
2112 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(SIP_TO_CORE_PRIVATE->buddies));
2113 while (entry) {
2114 b = entry->data;
2115 g = purple_buddy_get_group(b);
2116 buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, b->name);
2117 if(buddy) {
2118 gboolean in_sipe_groups = FALSE;
2119 GSList *entry2 = buddy->groups;
2120 while (entry2) {
2121 struct sipe_group *group = entry2->data;
2122 if (sipe_strequal(group->name, g->name)) {
2123 in_sipe_groups = TRUE;
2124 break;
2126 entry2 = entry2->next;
2128 if(!in_sipe_groups) {
2129 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
2130 purple_blist_remove_buddy(b);
2132 } else {
2133 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
2134 purple_blist_remove_buddy(b);
2136 entry = entry->next;
2138 g_slist_free(buddies);
2141 static int
2142 sipe_find_access_level(struct sipe_account_data *sip,
2143 const gchar *type,
2144 const gchar *value,
2145 gboolean *is_group_access);
2147 static void
2148 sipe_refresh_blocked_status_cb(char *buddy_name,
2149 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
2150 struct sipe_account_data *sip)
2152 int container_id = sipe_find_access_level(sip, "user", buddy_name, NULL);
2153 gboolean blocked = (container_id == 32000);
2154 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
2156 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
2157 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
2159 if (blocked != blocked_in_blist) {
2160 if (blocked) {
2161 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
2162 } else {
2163 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
2166 /* stupid workaround to make pidgin re-render screen to reflect our changes */
2168 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
2169 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
2170 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
2172 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
2173 sipe_got_user_status(sip, buddy_name, purple_status_get_id(pstatus));
2179 static void
2180 sipe_refresh_blocked_status(struct sipe_account_data *sip)
2182 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc) sipe_refresh_blocked_status_cb , (gpointer)sip);
2185 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2187 int len = msg->bodylen;
2189 const gchar *tmp = sipmsg_find_header(msg, "Event");
2190 const sipe_xml *item;
2191 sipe_xml *isc;
2192 const gchar *contacts_delta;
2193 const sipe_xml *group_node;
2194 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
2195 return FALSE;
2198 /* Convert the contact from XML to Purple Buddies */
2199 isc = sipe_xml_parse(msg->body, len);
2200 if (!isc) {
2201 return FALSE;
2204 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
2205 if (contacts_delta) {
2206 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2209 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
2211 /* Parse groups */
2212 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
2213 struct sipe_group * group = g_new0(struct sipe_group, 1);
2214 const char *name = sipe_xml_attribute(group_node, "name");
2216 if (g_str_has_prefix(name, "~")) {
2217 name = _("Other Contacts");
2219 group->name = g_strdup(name);
2220 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
2222 sipe_group_add(sip, group);
2225 // Make sure we have at least one group
2226 if (g_slist_length(sip->groups) == 0) {
2227 struct sipe_group * group = g_new0(struct sipe_group, 1);
2228 PurpleGroup *purple_group;
2229 group->name = g_strdup(_("Other Contacts"));
2230 group->id = 1;
2231 purple_group = purple_group_new(group->name);
2232 purple_blist_add_group(purple_group, NULL);
2233 sip->groups = g_slist_append(sip->groups, group);
2236 /* Parse contacts */
2237 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
2238 const gchar *uri = sipe_xml_attribute(item, "uri");
2239 const gchar *name = sipe_xml_attribute(item, "name");
2240 gchar *buddy_name;
2241 struct sipe_buddy *buddy = NULL;
2242 gchar *tmp;
2243 gchar **item_groups;
2244 int i = 0;
2246 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2247 tmp = sip_uri_from_name(uri);
2248 buddy_name = g_ascii_strdown(tmp, -1);
2249 g_free(tmp);
2251 /* assign to group Other Contacts if nothing else received */
2252 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
2253 if(is_empty(tmp)) {
2254 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2255 g_free(tmp);
2256 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2258 item_groups = g_strsplit(tmp, " ", 0);
2259 g_free(tmp);
2261 while (item_groups[i]) {
2262 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2264 // If couldn't find the right group for this contact, just put them in the first group we have
2265 if (group == NULL && g_slist_length(sip->groups) > 0) {
2266 group = sip->groups->data;
2269 if (group != NULL) {
2270 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2271 if (!b){
2272 b = purple_buddy_new(sip->account, buddy_name, uri);
2273 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2275 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
2278 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
2279 if (name != NULL && strlen(name) != 0) {
2280 purple_blist_alias_buddy(b, name);
2282 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
2286 if (!buddy) {
2287 buddy = g_new0(struct sipe_buddy, 1);
2288 buddy->name = g_strdup(b->name);
2289 g_hash_table_insert(SIP_TO_CORE_PRIVATE->buddies, buddy->name, buddy);
2292 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2294 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
2295 } else {
2296 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2297 name);
2300 i++;
2301 } // while, contact groups
2302 g_strfreev(item_groups);
2303 g_free(buddy_name);
2305 } // for, contacts
2307 sipe_cleanup_local_blist(sip);
2309 /* Add self-contact if not there yet. 2005 systems. */
2310 /* This will resemble subscription to roaming_self in 2007 systems */
2311 if (!sip->ocs2007) {
2312 gchar *self_uri = sip_uri_self(sip);
2313 struct sipe_buddy *buddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, self_uri);
2315 if (!buddy) {
2316 buddy = g_new0(struct sipe_buddy, 1);
2317 buddy->name = g_strdup(self_uri);
2318 g_hash_table_insert(SIP_TO_CORE_PRIVATE->buddies, buddy->name, buddy);
2320 g_free(self_uri);
2323 sipe_xml_free(isc);
2325 /* subscribe to buddies */
2326 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2327 if (sip->batched_support) {
2328 sipe_subscribe_presence_batched(sip, NULL);
2329 } else {
2330 g_hash_table_foreach(SIP_TO_CORE_PRIVATE->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2332 sip->subscribed_buddies = TRUE;
2334 /* for 2005 systems schedule contacts' status update
2335 * based on their calendar information
2337 if (!sip->ocs2007) {
2338 sipe_sched_calendar_status_update(sip, time(NULL));
2341 return 0;
2345 * Subscribe roaming contacts
2347 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2349 gchar *to = sip_uri_self(sip);
2350 gchar *tmp = get_contact(sip);
2351 gchar *hdr = g_strdup_printf(
2352 "Event: vnd-microsoft-roaming-contacts\r\n"
2353 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2354 "Supported: com.microsoft.autoextend\r\n"
2355 "Supported: ms-benotify\r\n"
2356 "Proxy-Require: ms-benotify\r\n"
2357 "Supported: ms-piggyback-first-notify\r\n"
2358 "Contact: %s\r\n", tmp);
2359 g_free(tmp);
2361 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2362 g_free(to);
2363 g_free(hdr);
2366 static void sipe_subscribe_presence_wpending(struct sipe_core_private *sipe_private,
2367 SIPE_UNUSED_PARAMETER void *unused)
2369 struct sipe_account_data *sip = sipe_private->temporary;
2370 gchar *key;
2371 struct sip_dialog *dialog;
2372 gchar *to = sip_uri_self(sip);
2373 gchar *tmp = get_contact(sip);
2374 gchar *hdr = g_strdup_printf(
2375 "Event: presence.wpending\r\n"
2376 "Accept: text/xml+msrtc.wpending\r\n"
2377 "Supported: com.microsoft.autoextend\r\n"
2378 "Supported: ms-benotify\r\n"
2379 "Proxy-Require: ms-benotify\r\n"
2380 "Supported: ms-piggyback-first-notify\r\n"
2381 "Contact: %s\r\n", tmp);
2382 g_free(tmp);
2384 /* Subscription is identified by <event> key */
2385 key = g_strdup_printf("<%s>", "presence.wpending");
2386 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2387 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
2389 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2391 g_free(to);
2392 g_free(hdr);
2393 g_free(key);
2397 * Fires on deregistration event initiated by server.
2398 * [MS-SIPREGE] SIP extension.
2401 // 2007 Example
2403 // Content-Type: text/registration-event
2404 // subscription-state: terminated;expires=0
2405 // ms-diagnostics-public: 4141;reason="User disabled"
2407 // deregistered;event=rejected
2409 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2411 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2412 gchar *event = NULL;
2413 gchar *reason = NULL;
2414 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
2415 gchar *warning;
2417 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
2418 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2420 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2421 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2422 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2423 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2424 } else {
2425 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2426 return;
2429 if (diagnostics != NULL) {
2430 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
2431 } else { // for LCS2005
2432 int error_id = 0;
2433 if (event && sipe_strcase_equal(event, "unregistered")) {
2434 error_id = 4140; // [MS-SIPREGE]
2435 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2436 reason = g_strdup(_("you are already signed in at another location"));
2437 } else if (event && sipe_strcase_equal(event, "rejected")) {
2438 error_id = 4141;
2439 reason = g_strdup(_("user disabled")); // [MS-OCER]
2440 } else if (event && sipe_strcase_equal(event, "deactivated")) {
2441 error_id = 4142;
2442 reason = g_strdup(_("user moved")); // [MS-OCER]
2445 g_free(event);
2446 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2447 g_free(reason);
2449 purple_connection_error_reason(sip->gc,
2450 PURPLE_CONNECTION_ERROR_INVALID_USERNAME,
2451 warning);
2452 g_free(warning);
2456 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2458 sipe_xml *xn_provision_group_list;
2459 const sipe_xml *node;
2461 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
2463 /* provisionGroup */
2464 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
2465 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
2466 g_free(sip->focus_factory_uri);
2467 sip->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
2468 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2469 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2470 break;
2473 sipe_xml_free(xn_provision_group_list);
2476 /** for 2005 system */
2477 static void
2478 sipe_process_provisioning(struct sipe_account_data *sip,
2479 struct sipmsg *msg)
2481 sipe_xml *xn_provision;
2482 const sipe_xml *node;
2484 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
2485 if ((node = sipe_xml_child(xn_provision, "user"))) {
2486 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
2487 if ((node = sipe_xml_child(node, "line"))) {
2488 const gchar *line_uri = sipe_xml_attribute(node, "uri");
2489 const gchar *server = sipe_xml_attribute(node, "server");
2490 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2491 sip_csta_open(sip, line_uri, server);
2494 sipe_xml_free(xn_provision);
2497 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2499 const gchar *contacts_delta;
2500 sipe_xml *xml;
2502 xml = sipe_xml_parse(msg->body, msg->bodylen);
2503 if (!xml)
2505 return;
2508 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
2509 if (contacts_delta)
2511 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2514 sipe_xml_free(xml);
2517 static void
2518 free_container_member(struct sipe_container_member *member)
2520 if (!member) return;
2522 g_free(member->type);
2523 g_free(member->value);
2524 g_free(member);
2527 static void
2528 free_container(struct sipe_container *container)
2530 GSList *entry;
2532 if (!container) return;
2534 entry = container->members;
2535 while (entry) {
2536 void *data = entry->data;
2537 entry = g_slist_remove(entry, data);
2538 free_container_member((struct sipe_container_member *)data);
2540 g_free(container);
2543 static void
2544 sipe_send_container_members_prepare(const guint container_id,
2545 const guint container_version,
2546 const gchar *action,
2547 const gchar *type,
2548 const gchar *value,
2549 char **container_xmls)
2551 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2552 gchar *body;
2554 if (!container_xmls) return;
2556 body = g_strdup_printf(
2557 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2558 container_id,
2559 container_version,
2560 action,
2561 type,
2562 value_str);
2563 g_free(value_str);
2565 if ((*container_xmls) == NULL) {
2566 *container_xmls = body;
2567 } else {
2568 char *tmp = *container_xmls;
2570 *container_xmls = g_strconcat(*container_xmls, body, NULL);
2571 g_free(tmp);
2572 g_free(body);
2576 static void
2577 sipe_send_set_container_members(struct sipe_account_data *sip,
2578 char *container_xmls)
2580 gchar *self;
2581 gchar *contact;
2582 gchar *hdr;
2583 gchar *body;
2585 if (!container_xmls) return;
2587 self = sip_uri_self(sip);
2588 body = g_strdup_printf(
2589 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2590 "%s"
2591 "</setContainerMembers>",
2592 container_xmls);
2594 contact = get_contact(sip);
2595 hdr = g_strdup_printf("Contact: %s\r\n"
2596 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2597 g_free(contact);
2599 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2601 g_free(hdr);
2602 g_free(body);
2603 g_free(self);
2607 * Finds locally stored MS-PRES container member
2609 static struct sipe_container_member *
2610 sipe_find_container_member(struct sipe_container *container,
2611 const gchar *type,
2612 const gchar *value)
2614 struct sipe_container_member *member;
2615 GSList *entry;
2617 if (container == NULL || type == NULL) {
2618 return NULL;
2621 entry = container->members;
2622 while (entry) {
2623 member = entry->data;
2624 if (sipe_strcase_equal(member->type, type) &&
2625 sipe_strcase_equal(member->value, value))
2627 return member;
2629 entry = entry->next;
2631 return NULL;
2635 * Finds locally stored MS-PRES container by id
2637 static struct sipe_container *
2638 sipe_find_container(struct sipe_account_data *sip,
2639 guint id)
2641 struct sipe_container *container;
2642 GSList *entry;
2644 if (sip == NULL) {
2645 return NULL;
2648 entry = sip->containers;
2649 while (entry) {
2650 container = entry->data;
2651 if (id == container->id) {
2652 return container;
2654 entry = entry->next;
2656 return NULL;
2659 static GSList *
2660 sipe_get_access_domains(struct sipe_account_data *sip)
2662 struct sipe_container *container;
2663 struct sipe_container_member *member;
2664 GSList *entry;
2665 GSList *entry2;
2666 GSList *res = NULL;
2668 if (!sip) return NULL;
2670 entry = sip->containers;
2671 while (entry) {
2672 container = entry->data;
2674 entry2 = container->members;
2675 while (entry2) {
2676 member = entry2->data;
2677 if (sipe_strcase_equal(member->type, "domain"))
2679 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
2681 entry2 = entry2->next;
2683 entry = entry->next;
2685 return res;
2689 * Returns pointer to domain part in provided Email URL
2691 * @param email an email URL. Example: first.last@hq.company.com
2692 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
2694 * Doesn't allocate memory
2696 static const char *
2697 sipe_get_domain(const char *email)
2699 char *tmp;
2701 if (!email) return NULL;
2703 tmp = strstr(email, "@");
2705 if (tmp && ((tmp+1) < (email + strlen(email)))) {
2706 return tmp+1;
2707 } else {
2708 return NULL;
2713 /* @TODO: replace with binary search for faster access? */
2714 /** source: http://support.microsoft.com/kb/897567 */
2715 static const char * const public_domains [] = {
2716 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
2717 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
2718 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
2719 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
2720 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
2721 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
2722 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
2723 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
2724 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
2725 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
2726 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
2727 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
2728 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
2729 "yahoo.com",
2730 NULL};
2732 static gboolean
2733 sipe_is_public_domain(const char *domain)
2735 int i = 0;
2736 while (public_domains[i]) {
2737 if (sipe_strcase_equal(public_domains[i], domain)) {
2738 return TRUE;
2740 i++;
2742 return FALSE;
2746 * Access Levels
2747 * 32000 - Blocked
2748 * 400 - Personal
2749 * 300 - Team
2750 * 200 - Company
2751 * 100 - Public
2753 static const char *
2754 sipe_get_access_level_name(int container_id)
2756 switch(container_id) {
2757 case 32000: return _("Blocked");
2758 case 400: return _("Personal");
2759 case 300: return _("Team");
2760 case 200: return _("Company");
2761 case 100: return _("Public");
2763 return _("Unknown");
2766 static const guint containers[] = {32000, 400, 300, 200, 100};
2767 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
2770 static int
2771 sipe_find_member_access_level(struct sipe_account_data *sip,
2772 const gchar *type,
2773 const gchar *value)
2775 unsigned int i = 0;
2776 const gchar *value_mod = value;
2778 if (!type) return -1;
2780 if (sipe_strequal("user", type)) {
2781 value_mod = sipe_get_no_sip_uri(value);
2784 for (i = 0; i < CONTAINERS_LEN; i++) {
2785 struct sipe_container_member *member;
2786 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2787 if (!container) continue;
2789 member = sipe_find_container_member(container, type, value_mod);
2790 if (member) return containers[i];
2793 return -1;
2796 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
2797 static int
2798 sipe_find_access_level(struct sipe_account_data *sip,
2799 const gchar *type,
2800 const gchar *value,
2801 gboolean *is_group_access)
2803 int container_id = -1;
2805 if (sipe_strequal("user", type)) {
2806 const char *domain;
2807 const char *no_sip_uri = sipe_get_no_sip_uri(value);
2809 container_id = sipe_find_member_access_level(sip, "user", no_sip_uri);
2810 if (container_id >= 0) {
2811 if (is_group_access) *is_group_access = FALSE;
2812 return container_id;
2815 domain = sipe_get_domain(no_sip_uri);
2816 container_id = sipe_find_member_access_level(sip, "domain", domain);
2817 if (container_id >= 0) {
2818 if (is_group_access) *is_group_access = TRUE;
2819 return container_id;
2822 container_id = sipe_find_member_access_level(sip, "sameEnterprise", NULL);
2823 if ((container_id >= 0) && sipe_strcase_equal(SIP_TO_CORE_PUBLIC->sip_domain, domain)) {
2824 if (is_group_access) *is_group_access = TRUE;
2825 return container_id;
2828 container_id = sipe_find_member_access_level(sip, "publicCloud", NULL);
2829 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
2830 if (is_group_access) *is_group_access = TRUE;
2831 return container_id;
2834 container_id = sipe_find_member_access_level(sip, "everyone", NULL);
2835 if ((container_id >= 0)) {
2836 if (is_group_access) *is_group_access = TRUE;
2837 return container_id;
2839 } else {
2840 container_id = sipe_find_member_access_level(sip, type, value);
2841 if (is_group_access) *is_group_access = FALSE;
2844 return container_id;
2848 * @param container_id a new access level. If -1 then current access level
2849 * is just removed (I.e. the member is removed from all containers).
2850 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
2851 * @param value a value for member. E.g. SIP URI for "user" member type.
2853 static void
2854 sipe_change_access_level(struct sipe_account_data *sip,
2855 const int container_id,
2856 const gchar *type,
2857 const gchar *value)
2859 unsigned int i;
2860 int current_container_id = -1;
2861 char *container_xmls = NULL;
2863 /* for each container: find/delete */
2864 for (i = 0; i < CONTAINERS_LEN; i++) {
2865 struct sipe_container_member *member;
2866 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2868 if (!container) continue;
2870 member = sipe_find_container_member(container, type, value);
2871 if (member) {
2872 current_container_id = containers[i];
2873 /* delete/publish current access level */
2874 if (container_id < 0 || container_id != current_container_id) {
2875 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
2876 /* remove member from our cache, to be able to recalculate AL below */
2877 container->members = g_slist_remove(container->members, member);
2878 current_container_id = -1;
2883 /* recalculate AL below */
2884 current_container_id = sipe_find_access_level(sip, type, value, NULL);
2886 /* assign/publish new access level */
2887 if (container_id != current_container_id && container_id >= 0) {
2888 struct sipe_container *container = sipe_find_container(sip, container_id);
2889 guint version = container ? container->version : 0;
2891 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
2894 if (container_xmls) {
2895 sipe_send_set_container_members(sip, container_xmls);
2897 g_free(container_xmls);
2900 static void
2901 free_publication(struct sipe_publication *publication)
2903 g_free(publication->category);
2904 g_free(publication->cal_event_hash);
2905 g_free(publication->note);
2907 g_free(publication->working_hours_xml_str);
2908 g_free(publication->fb_start_str);
2909 g_free(publication->free_busy_base64);
2911 g_free(publication);
2914 /* key is <category><instance><container> */
2915 static gboolean
2916 sipe_is_our_publication(struct sipe_account_data *sip,
2917 const gchar *key)
2919 GSList *entry;
2921 /* filling keys for our publications if not yet cached */
2922 if (!sip->our_publication_keys) {
2923 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2924 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2925 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2926 guint calendar_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
2927 guint cal_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF);
2928 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
2929 guint note_oof_instance = sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF);
2931 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2932 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2933 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2934 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2935 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2936 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2937 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2938 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2939 SIPE_DEBUG_INFO("\tNote : %u", 0);
2940 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2942 /* device */
2943 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2944 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2946 /* state:machineState */
2947 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2948 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2949 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2950 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2952 /* state:userState */
2953 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2954 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2955 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2956 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2958 /* state:calendarState */
2959 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2960 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2961 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2962 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2964 /* state:calendarState OOF */
2965 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2966 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2967 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2968 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2970 /* note */
2971 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2972 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2973 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2974 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2975 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2976 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2978 /* note OOF */
2979 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2980 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2981 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2982 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2983 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2984 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2986 /* calendarData:WorkingHours */
2987 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2988 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2989 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2990 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2991 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2992 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2993 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2994 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2995 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2996 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2997 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2998 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3000 /* calendarData:FreeBusy */
3001 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3002 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
3003 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3004 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
3005 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3006 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
3007 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3008 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
3009 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3010 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
3011 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
3012 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
3014 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3015 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3018 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3020 entry = sip->our_publication_keys;
3021 while (entry) {
3022 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3023 if (sipe_strequal(entry->data, key)) {
3024 return TRUE;
3026 entry = entry->next;
3028 return FALSE;
3031 /** Property names to store in blist.xml */
3032 #define ALIAS_PROP "alias"
3033 #define EMAIL_PROP "email"
3034 #define PHONE_PROP "phone"
3035 #define PHONE_DISPLAY_PROP "phone-display"
3036 #define PHONE_MOBILE_PROP "phone-mobile"
3037 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3038 #define PHONE_HOME_PROP "phone-home"
3039 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3040 #define PHONE_OTHER_PROP "phone-other"
3041 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3042 #define PHONE_CUSTOM1_PROP "phone-custom1"
3043 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3044 #define SITE_PROP "site"
3045 #define COMPANY_PROP "company"
3046 #define DEPARTMENT_PROP "department"
3047 #define TITLE_PROP "title"
3048 #define OFFICE_PROP "office"
3049 /** implies work address */
3050 #define ADDRESS_STREET_PROP "address-street"
3051 #define ADDRESS_CITY_PROP "address-city"
3052 #define ADDRESS_STATE_PROP "address-state"
3053 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3054 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3057 * Tries to figure out user first and last name
3058 * based on Display Name and email properties.
3060 * Allocates memory - must be g_free()'d
3062 * Examples to parse:
3063 * First Last
3064 * First Last - Company Name
3065 * Last, First
3066 * Last, First M.
3067 * Last, First (C)(STP) (Company)
3068 * first.last@company.com (preprocessed as "first last")
3069 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3071 * Unusable examples:
3072 * user@company.com (preprocessed as "user")
3073 * first.m.last@company.com (preprocessed as "first m last")
3074 * user.company.com@reuters.net (preprocessed as "user company com")
3076 static void
3077 sipe_get_first_last_names(struct sipe_account_data *sip,
3078 const char *uri,
3079 char **first_name,
3080 char **last_name)
3082 PurpleBuddy *p_buddy;
3083 char *display_name;
3084 const char *email;
3085 const char *first, *last;
3086 char *tmp;
3087 char **parts;
3088 gboolean has_comma = FALSE;
3090 if (!sip || !uri) return;
3092 p_buddy = purple_find_buddy(sip->account, uri);
3094 if (!p_buddy) return;
3096 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
3097 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
3099 if (!display_name && !email) return;
3101 /* if no display name, make "first last anything_else" out of email */
3102 if (email && !display_name) {
3103 display_name = g_strndup(email, strstr(email, "@") - email);
3104 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
3105 g_free(tmp);
3108 if (display_name) {
3109 has_comma = (strstr(display_name, ",") != NULL);
3110 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
3111 g_free(tmp);
3112 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
3113 g_free(tmp);
3116 parts = g_strsplit(display_name, " ", 0);
3118 if (!parts[0] || !parts[1]) {
3119 g_free(display_name);
3120 g_strfreev(parts);
3121 return;
3124 if (has_comma) {
3125 last = parts[0];
3126 first = parts[1];
3127 } else {
3128 first = parts[0];
3129 last = parts[1];
3132 if (first_name) {
3133 *first_name = g_strstrip(g_strdup(first));
3136 if (last_name) {
3137 *last_name = g_strstrip(g_strdup(last));
3140 g_free(display_name);
3141 g_strfreev(parts);
3145 * Update user information
3147 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3148 * @param property_name
3149 * @param property_value may be modified to strip white space
3151 static void
3152 sipe_update_user_info(struct sipe_account_data *sip,
3153 const char *uri,
3154 const char *property_name,
3155 char *property_value)
3157 GSList *buddies, *entry;
3159 if (!property_name || strlen(property_name) == 0) return;
3161 if (property_value)
3162 property_value = g_strstrip(property_value);
3164 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
3165 while (entry) {
3166 const char *prop_str;
3167 const char *server_alias;
3168 PurpleBuddy *p_buddy = entry->data;
3170 /* for Display Name */
3171 if (sipe_strequal(property_name, ALIAS_PROP)) {
3172 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
3173 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
3174 purple_blist_alias_buddy(p_buddy, property_value);
3177 server_alias = purple_buddy_get_server_alias(p_buddy);
3178 if (!is_empty(property_value) &&
3179 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
3181 purple_blist_server_alias_buddy(p_buddy, property_value);
3184 /* for other properties */
3185 else {
3186 if (!is_empty(property_value)) {
3187 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
3188 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
3189 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
3194 entry = entry->next;
3196 g_slist_free(buddies);
3200 * Update user phone
3201 * Suitable for both 2005 and 2007 systems.
3203 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3204 * @param phone_type
3205 * @param phone may be modified to strip white space
3206 * @param phone_display_string may be modified to strip white space
3208 static void
3209 sipe_update_user_phone(struct sipe_account_data *sip,
3210 const char *uri,
3211 const gchar *phone_type,
3212 gchar *phone,
3213 gchar *phone_display_string)
3215 const char *phone_node = PHONE_PROP; /* work phone by default */
3216 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
3218 if(!phone || strlen(phone) == 0) return;
3220 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
3221 phone_node = PHONE_MOBILE_PROP;
3222 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
3223 } else if (sipe_strequal(phone_type, "home")) {
3224 phone_node = PHONE_HOME_PROP;
3225 phone_display_node = PHONE_HOME_DISPLAY_PROP;
3226 } else if (sipe_strequal(phone_type, "other")) {
3227 phone_node = PHONE_OTHER_PROP;
3228 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
3229 } else if (sipe_strequal(phone_type, "custom1")) {
3230 phone_node = PHONE_CUSTOM1_PROP;
3231 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
3234 sipe_update_user_info(sip, uri, phone_node, phone);
3235 if (phone_display_string) {
3236 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
3240 void
3241 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
3243 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
3244 const char* calendar = purple_account_get_string(sip->account, "calendar", "EXCH");
3246 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
3248 if (sipe_strequal(calendar, "EXCH")) {
3249 sipe_ews_update_calendar(sip);
3250 } else if (sipe_strequal(calendar, "DOMINO")) {
3251 sipe_domino_update_calendar(sip);
3254 /* schedule repeat */
3255 sipe_schedule_action("<+update-calendar>",
3256 UPDATE_CALENDAR_INTERVAL,
3257 (Action)sipe_core_update_calendar,
3258 NULL,
3259 SIP_TO_CORE_PRIVATE,
3260 NULL);
3262 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
3266 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3267 * by using standard Purple's means of signals and saved statuses.
3269 * Thus all UI elements get updated: Status Button with Note, docklet.
3270 * This is ablolutely important as both our status and note can come
3271 * inbound (roaming) or be updated programmatically (e.g. based on our
3272 * calendar data).
3274 static void
3275 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
3276 const char *status_id,
3277 const char *message,
3278 time_t do_not_publish[])
3280 PurpleStatus *status = purple_account_get_active_status(account);
3281 gboolean changed = TRUE;
3283 if (g_str_equal(status_id, purple_status_get_id(status)) &&
3284 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
3286 changed = FALSE;
3289 if (purple_savedstatus_is_idleaway()) {
3290 changed = FALSE;
3293 if (changed) {
3294 PurpleSavedStatus *saved_status;
3295 const PurpleStatusType *acct_status_type =
3296 purple_status_type_find_with_id(account->status_types, status_id);
3297 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
3298 sipe_activity activity = sipe_get_activity_by_token(status_id);
3300 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
3301 if (saved_status) {
3302 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
3305 /* If this type+message is unique then create a new transient saved status
3306 * Ref: gtkstatusbox.c
3308 if (!saved_status) {
3309 GList *tmp;
3310 GList *active_accts = purple_accounts_get_all_active();
3312 saved_status = purple_savedstatus_new(NULL, primitive);
3313 purple_savedstatus_set_message(saved_status, message);
3315 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
3316 purple_savedstatus_set_substatus(saved_status,
3317 (PurpleAccount *)tmp->data, acct_status_type, message);
3319 g_list_free(active_accts);
3322 do_not_publish[activity] = time(NULL);
3323 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3324 status_id, (int)do_not_publish[activity]);
3326 /* Set the status for each account */
3327 purple_savedstatus_activate(saved_status);
3331 struct hash_table_delete_payload {
3332 GHashTable *hash_table;
3333 guint container;
3336 static void
3337 sipe_remove_category_container_publications_cb(const char *name,
3338 struct sipe_publication *publication,
3339 struct hash_table_delete_payload *payload)
3341 if (publication->container == payload->container) {
3342 g_hash_table_remove(payload->hash_table, name);
3345 static void
3346 sipe_remove_category_container_publications(GHashTable *our_publications,
3347 const char *category,
3348 guint container)
3350 struct hash_table_delete_payload payload;
3351 payload.hash_table = g_hash_table_lookup(our_publications, category);
3353 if (!payload.hash_table) return;
3355 payload.container = container;
3356 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
3359 static void
3360 send_publish_category_initial(struct sipe_account_data *sip);
3363 * When we receive some self (BE) NOTIFY with a new subscriber
3364 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3367 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
3369 gchar *contact;
3370 gchar *to;
3371 sipe_xml *xml;
3372 const sipe_xml *node;
3373 const sipe_xml *node2;
3374 char *display_name = NULL;
3375 char *uri;
3376 GSList *category_names = NULL;
3377 int aggreg_avail = 0;
3378 static sipe_activity aggreg_activity = SIPE_ACTIVITY_UNSET;
3379 gboolean do_update_status = FALSE;
3380 gboolean has_note_cleaned = FALSE;
3382 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3384 xml = sipe_xml_parse(msg->body, msg->bodylen);
3385 if (!xml) return;
3387 contact = get_contact(sip);
3388 to = sip_uri_self(sip);
3391 /* categories */
3392 /* set list of categories participating in this XML */
3393 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3394 const gchar *name = sipe_xml_attribute(node, "name");
3395 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
3397 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3398 category_names ? (int) g_slist_length(category_names) : -1);
3399 /* drop category information */
3400 if (category_names) {
3401 GSList *entry = category_names;
3402 while (entry) {
3403 GHashTable *cat_publications;
3404 const gchar *category = entry->data;
3405 entry = entry->next;
3406 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
3407 cat_publications = g_hash_table_lookup(sip->our_publications, category);
3408 if (cat_publications) {
3409 g_hash_table_remove(sip->our_publications, category);
3410 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
3414 g_slist_free(category_names);
3415 /* filling our categories reflected in roaming data */
3416 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
3417 const char *tmp;
3418 const gchar *name = sipe_xml_attribute(node, "name");
3419 guint container = sipe_xml_int_attribute(node, "container", -1);
3420 guint instance = sipe_xml_int_attribute(node, "instance", -1);
3421 guint version = sipe_xml_int_attribute(node, "version", 0);
3422 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
3423 sipe_utils_str_to_time(tmp) : 0;
3424 gchar *key;
3425 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
3427 /* Ex. clear note: <category name="note"/> */
3428 if (container == (guint)-1) {
3429 g_free(sip->note);
3430 sip->note = NULL;
3431 do_update_status = TRUE;
3432 continue;
3435 /* Ex. clear note: <category name="note" container="200"/> */
3436 if (instance == (guint)-1) {
3437 if (container == 200) {
3438 g_free(sip->note);
3439 sip->note = NULL;
3440 do_update_status = TRUE;
3442 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
3443 sipe_remove_category_container_publications(
3444 sip->our_publications, name, container);
3445 continue;
3448 /* key is <category><instance><container> */
3449 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
3450 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
3452 /* capture all userState publication for later clean up if required */
3453 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
3454 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3456 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
3457 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3458 publication->category = g_strdup(name);
3459 publication->instance = instance;
3460 publication->container = container;
3461 publication->version = version;
3463 if (!sip->user_state_publications) {
3464 sip->user_state_publications = g_hash_table_new_full(
3465 g_str_hash, g_str_equal,
3466 g_free, (GDestroyNotify)free_publication);
3468 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
3469 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3470 key, version);
3474 if (sipe_is_our_publication(sip, key)) {
3475 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3477 publication->category = g_strdup(name);
3478 publication->instance = instance;
3479 publication->container = container;
3480 publication->version = version;
3482 /* filling publication->availability */
3483 if (sipe_strequal(name, "state")) {
3484 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3485 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3487 if (xn_avail) {
3488 gchar *avail_str = sipe_xml_data(xn_avail);
3489 if (avail_str) {
3490 publication->availability = atoi(avail_str);
3492 g_free(avail_str);
3494 /* for calendarState */
3495 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
3496 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3497 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3499 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
3500 if (xn_activity) {
3501 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
3502 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3504 event->is_meeting = TRUE;
3507 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
3508 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
3510 publication->cal_event_hash = sipe_cal_event_hash(event);
3511 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3512 publication->cal_event_hash);
3513 sipe_cal_event_free(event);
3516 /* filling publication->note */
3517 if (sipe_strequal(name, "note")) {
3518 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
3520 if (!has_note_cleaned) {
3521 has_note_cleaned = TRUE;
3523 g_free(sip->note);
3524 sip->note = NULL;
3525 sip->note_since = publish_time;
3527 do_update_status = TRUE;
3530 g_free(publication->note);
3531 publication->note = NULL;
3532 if (xn_body) {
3533 char *tmp;
3535 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
3536 g_free(tmp);
3537 if (publish_time >= sip->note_since) {
3538 g_free(sip->note);
3539 sip->note = g_strdup(publication->note);
3540 sip->note_since = publish_time;
3541 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
3543 do_update_status = TRUE;
3548 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3549 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3550 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
3551 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
3552 if (xn_free_busy) {
3553 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3554 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
3556 if (xn_working_hours) {
3557 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
3561 if (!cat_publications) {
3562 cat_publications = g_hash_table_new_full(
3563 g_str_hash, g_str_equal,
3564 g_free, (GDestroyNotify)free_publication);
3565 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3566 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3568 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3569 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3571 g_free(key);
3573 /* aggregateState (not an our publication) from 2-nd container */
3574 if (sipe_strequal(name, "state") && container == 2) {
3575 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3577 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
3578 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3579 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3581 if (xn_avail) {
3582 gchar *avail_str = sipe_xml_data(xn_avail);
3583 if (avail_str) {
3584 aggreg_avail = atoi(avail_str);
3586 g_free(avail_str);
3589 if (xn_activity) {
3590 const char *activity_token = sipe_xml_attribute(xn_activity, "token");
3592 aggreg_activity = sipe_get_activity_by_token(activity_token);
3595 do_update_status = TRUE;
3599 /* userProperties published by server from AD */
3600 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3601 const sipe_xml *line;
3602 /* line, for Remote Call Control (RCC) */
3603 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
3604 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
3605 const gchar *line_type = sipe_xml_attribute(line, "lineType");
3606 gchar *line_uri;
3608 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3610 line_uri = sipe_xml_data(line);
3611 if (line_uri) {
3612 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3613 sip_csta_open(sip, line_uri, line_server);
3615 g_free(line_uri);
3617 break;
3621 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3622 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3624 /* containers */
3625 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
3626 guint id = sipe_xml_int_attribute(node, "id", 0);
3627 struct sipe_container *container = sipe_find_container(sip, id);
3629 if (container) {
3630 sip->containers = g_slist_remove(sip->containers, container);
3631 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3632 free_container(container);
3634 container = g_new0(struct sipe_container, 1);
3635 container->id = id;
3636 container->version = sipe_xml_int_attribute(node, "version", 0);
3637 sip->containers = g_slist_append(sip->containers, container);
3638 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3640 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
3641 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3642 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
3643 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
3644 container->members = g_slist_append(container->members, member);
3645 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3646 member->type, member->value ? member->value : "");
3650 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3651 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
3652 char *container_xmls = NULL;
3653 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL, NULL);
3654 int federatedAL = sipe_find_access_level(sip, "federated", NULL, NULL);
3656 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3657 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3658 /* initial set-up to let counterparties see your status */
3659 if (sameEnterpriseAL < 0) {
3660 struct sipe_container *container = sipe_find_container(sip, 200);
3661 guint version = container ? container->version : 0;
3662 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
3664 if (federatedAL < 0) {
3665 struct sipe_container *container = sipe_find_container(sip, 100);
3666 guint version = container ? container->version : 0;
3667 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
3669 sip->access_level_set = TRUE;
3671 if (container_xmls) {
3672 sipe_send_set_container_members(sip, container_xmls);
3674 g_free(container_xmls);
3677 /* Refresh contacts' blocked status */
3678 sipe_refresh_blocked_status(sip);
3680 /* subscribers */
3681 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
3682 const char *user;
3683 const char *acknowledged;
3684 gchar *hdr;
3685 gchar *body;
3687 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
3688 if (!user) continue;
3689 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
3690 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
3691 uri = sip_uri_from_name(user);
3693 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
3695 acknowledged= sipe_xml_attribute(node, "acknowledged");
3696 if(sipe_strcase_equal(acknowledged,"false")){
3697 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
3698 if (!purple_find_buddy(sip->account, uri)) {
3699 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3702 hdr = g_strdup_printf(
3703 "Contact: %s\r\n"
3704 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3706 body = g_strdup_printf(
3707 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3708 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3709 "</setSubscribers>", user);
3711 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
3712 g_free(body);
3713 g_free(hdr);
3715 g_free(display_name);
3716 g_free(uri);
3719 g_free(contact);
3720 sipe_xml_free(xml);
3722 /* Publish initial state if not yet.
3723 * Assuming this happens on initial responce to subscription to roaming-self
3724 * so we've already updated our roaming data in full.
3725 * Only for 2007+
3727 if (!sip->initial_state_published) {
3728 send_publish_category_initial(sip);
3729 sip->initial_state_published = TRUE;
3730 /* dalayed run */
3731 sipe_schedule_action("<+update-calendar>",
3732 UPDATE_CALENDAR_DELAY,
3733 (Action)sipe_core_update_calendar,
3734 NULL,
3735 SIP_TO_CORE_PRIVATE,
3736 NULL);
3737 do_update_status = FALSE;
3738 } else if (aggreg_avail) {
3740 g_free(sip->status);
3741 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3742 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3743 } else {
3744 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3748 if (do_update_status) {
3749 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
3750 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3753 g_free(to);
3756 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
3758 gchar *to = sip_uri_self(sip);
3759 gchar *tmp = get_contact(sip);
3760 gchar *hdr = g_strdup_printf(
3761 "Event: vnd-microsoft-roaming-ACL\r\n"
3762 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3763 "Supported: com.microsoft.autoextend\r\n"
3764 "Supported: ms-benotify\r\n"
3765 "Proxy-Require: ms-benotify\r\n"
3766 "Supported: ms-piggyback-first-notify\r\n"
3767 "Contact: %s\r\n", tmp);
3768 g_free(tmp);
3770 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3771 g_free(to);
3772 g_free(hdr);
3776 * To request for presence information about the user, access level settings that have already been configured by the user
3777 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3778 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3781 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
3783 gchar *to = sip_uri_self(sip);
3784 gchar *tmp = get_contact(sip);
3785 gchar *hdr = g_strdup_printf(
3786 "Event: vnd-microsoft-roaming-self\r\n"
3787 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3788 "Supported: ms-benotify\r\n"
3789 "Proxy-Require: ms-benotify\r\n"
3790 "Supported: ms-piggyback-first-notify\r\n"
3791 "Contact: %s\r\n"
3792 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3794 gchar *body=g_strdup(
3795 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3796 "<roaming type=\"categories\"/>"
3797 "<roaming type=\"containers\"/>"
3798 "<roaming type=\"subscribers\"/></roamingList>");
3800 g_free(tmp);
3801 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3802 g_free(body);
3803 g_free(to);
3804 g_free(hdr);
3808 * For 2005 version
3810 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3812 gchar *to = sip_uri_self(sip);
3813 gchar *tmp = get_contact(sip);
3814 gchar *hdr = g_strdup_printf(
3815 "Event: vnd-microsoft-provisioning\r\n"
3816 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3817 "Supported: com.microsoft.autoextend\r\n"
3818 "Supported: ms-benotify\r\n"
3819 "Proxy-Require: ms-benotify\r\n"
3820 "Supported: ms-piggyback-first-notify\r\n"
3821 "Expires: 0\r\n"
3822 "Contact: %s\r\n", tmp);
3824 g_free(tmp);
3825 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3826 g_free(to);
3827 g_free(hdr);
3830 /** Subscription for provisioning information to help with initial
3831 * configuration. This subscription is a one-time query (denoted by the Expires header,
3832 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3833 * configuration, meeting policies, and policy settings that Communicator must enforce.
3834 * TODO: for what we need this information.
3837 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3839 gchar *to = sip_uri_self(sip);
3840 gchar *tmp = get_contact(sip);
3841 gchar *hdr = g_strdup_printf(
3842 "Event: vnd-microsoft-provisioning-v2\r\n"
3843 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3844 "Supported: com.microsoft.autoextend\r\n"
3845 "Supported: ms-benotify\r\n"
3846 "Proxy-Require: ms-benotify\r\n"
3847 "Supported: ms-piggyback-first-notify\r\n"
3848 "Expires: 0\r\n"
3849 "Contact: %s\r\n"
3850 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3851 gchar *body = g_strdup(
3852 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3853 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3854 "<provisioningGroup name=\"ucPolicy\"/>"
3855 "</provisioningGroupList>");
3857 g_free(tmp);
3858 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3859 g_free(body);
3860 g_free(to);
3861 g_free(hdr);
3864 static void
3865 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3866 gpointer value, gpointer user_data)
3868 struct sip_subscription *subscription = value;
3869 struct sip_dialog *dialog = &subscription->dialog;
3870 struct sipe_account_data *sip = user_data;
3871 gchar *tmp = get_contact(sip);
3872 gchar *hdr = g_strdup_printf(
3873 "Event: %s\r\n"
3874 "Expires: 0\r\n"
3875 "Contact: %s\r\n", subscription->event, tmp);
3876 g_free(tmp);
3878 /* Rate limit to max. 25 requests per seconds */
3879 g_usleep(1000000 / 25);
3881 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3882 g_free(hdr);
3885 /* IM Session (INVITE and MESSAGE methods) */
3887 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3888 static gchar *
3889 get_end_points (struct sipe_account_data *sip,
3890 struct sip_session *session)
3892 gchar *res;
3894 if (session == NULL) {
3895 return NULL;
3898 res = g_strdup_printf("<sip:%s>", sip->username);
3900 SIPE_DIALOG_FOREACH {
3901 gchar *tmp = res;
3902 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3903 g_free(tmp);
3905 if (dialog->theirepid) {
3906 tmp = res;
3907 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3908 g_free(tmp);
3910 } SIPE_DIALOG_FOREACH_END;
3912 return res;
3915 static gboolean
3916 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3917 struct sipmsg *msg,
3918 SIPE_UNUSED_PARAMETER struct transaction *trans)
3920 gboolean ret = TRUE;
3922 if (msg->response != 200) {
3923 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
3924 return FALSE;
3927 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
3929 return ret;
3933 * Asks UA/proxy about its capabilities.
3935 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3937 gchar *to = sip_uri(who);
3938 gchar *contact = get_contact(sip);
3939 gchar *request = g_strdup_printf(
3940 "Accept: application/sdp\r\n"
3941 "Contact: %s\r\n", contact);
3942 g_free(contact);
3944 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3946 g_free(to);
3947 g_free(request);
3950 static void
3951 sipe_notify_user(struct sipe_account_data *sip,
3952 struct sip_session *session,
3953 PurpleMessageFlags flags,
3954 const gchar *message)
3956 PurpleConversation *conv;
3958 if (!session->conv) {
3959 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3960 } else {
3961 conv = session->conv;
3963 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3966 void
3967 sipe_present_info(struct sipe_account_data *sip,
3968 struct sip_session *session,
3969 const gchar *message)
3971 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3974 static void
3975 sipe_present_err(struct sipe_account_data *sip,
3976 struct sip_session *session,
3977 const gchar *message)
3979 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3982 void
3983 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3984 struct sip_session *session,
3985 int sip_error,
3986 int sip_warning,
3987 const gchar *who,
3988 const gchar *message)
3990 char *msg, *msg_tmp, *msg_tmp2;
3991 const char *label;
3993 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
3994 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3995 g_free(msg_tmp);
3996 /* Service unavailable; Server Internal Error; Server Time-out */
3997 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
3998 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
3999 g_free(msg);
4000 msg = NULL;
4001 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
4002 label = _("This message was not delivered to %s because the service is not available");
4003 } else if (sip_error == 486) { /* Busy Here */
4004 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4005 } else if (sip_error == 415) { /* Unsupported media type */
4006 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
4007 } else {
4008 label = _("This message was not delivered to %s because one or more recipients are offline");
4011 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
4012 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
4013 msg ? ":" : "",
4014 msg ? msg : "");
4015 sipe_present_err(sip, session, msg_tmp);
4016 g_free(msg_tmp2);
4017 g_free(msg_tmp);
4018 g_free(msg);
4022 static gboolean
4023 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
4024 SIPE_UNUSED_PARAMETER struct transaction *trans)
4026 gboolean ret = TRUE;
4027 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4028 struct sip_session *session = sipe_session_find_im(sip, with);
4029 struct sip_dialog *dialog;
4030 gchar *cseq;
4031 char *key;
4032 struct queued_message *message;
4034 if (!session) {
4035 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4036 g_free(with);
4037 return FALSE;
4040 dialog = sipe_dialog_find(session, with);
4041 if (!dialog) {
4042 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4043 g_free(with);
4044 return FALSE;
4047 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4048 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
4049 g_free(cseq);
4050 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4052 if (msg->response >= 400) {
4053 PurpleBuddy *pbuddy;
4054 const char *alias = with;
4055 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4056 int warning = -1;
4058 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4060 if (warn_hdr) {
4061 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4062 if (parts[0]) {
4063 warning = atoi(parts[0]);
4065 g_strfreev(parts);
4068 /* cancel file transfer as rejected by server */
4069 if (msg->response == 606 && /* Not acceptable all. */
4070 warning == 309 && /* Message contents not allowed by policy */
4071 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4073 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
4074 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4075 sipe_utils_nameval_free(parsed_body);
4078 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4079 alias = purple_buddy_get_alias(pbuddy);
4082 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, (message ? message->body : NULL));
4084 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4085 if (msg->response == 408 || /* Request timeout */
4086 msg->response == 480 || /* Temporarily Unavailable */
4087 msg->response == 481) { /* Call/Transaction Does Not Exist */
4088 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4089 send_sip_request(sip->gc, "BYE", with, with, NULL, NULL, dialog, NULL);
4092 ret = FALSE;
4093 } else {
4094 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
4095 if (message_id) {
4096 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
4097 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4098 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
4101 g_hash_table_remove(session->unconfirmed_messages, key);
4102 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4103 key, g_hash_table_size(session->unconfirmed_messages));
4106 g_free(key);
4107 g_free(with);
4109 if (ret) sipe_im_process_queue(sip, session);
4110 return ret;
4113 static gboolean
4114 sipe_is_election_finished(struct sip_session *session);
4116 static void
4117 sipe_election_result(struct sipe_core_private *sipe_private,
4118 void *sess);
4120 static gboolean
4121 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
4122 SIPE_UNUSED_PARAMETER struct transaction *trans)
4124 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4125 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4126 struct sip_dialog *dialog;
4127 struct sip_session *session;
4129 session = sipe_session_find_chat_by_callid(sip, callid);
4130 if (!session) {
4131 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
4132 return FALSE;
4135 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
4136 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4137 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
4138 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
4140 if (xn_request_rm_response) {
4141 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
4142 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
4144 dialog = sipe_dialog_find(session, with);
4145 if (!dialog) {
4146 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
4147 sipe_xml_free(xn_action);
4148 return FALSE;
4151 if (allow && !g_strcasecmp(allow, "true")) {
4152 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
4153 dialog->election_vote = 1;
4154 } else if (allow && !g_strcasecmp(allow, "false")) {
4155 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
4156 dialog->election_vote = -1;
4159 if (sipe_is_election_finished(session)) {
4160 sipe_election_result(SIP_TO_CORE_PRIVATE,
4161 session);
4164 } else if (xn_set_rm_response) {
4167 sipe_xml_free(xn_action);
4171 return TRUE;
4174 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg, const char *content_type)
4176 gchar *hdr;
4177 gchar *tmp;
4178 char *msgtext = NULL;
4179 const gchar *msgr = "";
4180 gchar *tmp2 = NULL;
4182 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
4183 char *msgformat;
4184 gchar *msgr_value;
4186 sipe_parse_html(msg, &msgformat, &msgtext);
4187 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
4189 msgr_value = sipmsg_get_msgr_string(msgformat);
4190 g_free(msgformat);
4191 if (msgr_value) {
4192 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
4193 g_free(msgr_value);
4195 } else {
4196 msgtext = g_strdup(msg);
4199 tmp = get_contact(sip);
4200 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4201 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4202 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4203 if (content_type == NULL)
4204 content_type = "text/plain";
4206 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
4207 g_free(tmp);
4208 g_free(tmp2);
4210 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
4211 g_free(msgtext);
4212 g_free(hdr);
4216 void
4217 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
4219 GSList *entry2 = session->outgoing_message_queue;
4220 while (entry2) {
4221 struct queued_message *msg = entry2->data;
4223 /* for multiparty chat or conference */
4224 if (session->is_multiparty || session->focus_uri) {
4225 gchar *who = sip_uri_self(sip);
4226 serv_got_chat_in(sip->gc, session->chat_id, who,
4227 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
4228 g_free(who);
4231 SIPE_DIALOG_FOREACH {
4232 char *key;
4233 struct queued_message *message;
4235 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
4237 message = g_new0(struct queued_message,1);
4238 message->body = g_strdup(msg->body);
4239 if (msg->content_type != NULL)
4240 message->content_type = g_strdup(msg->content_type);
4242 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
4243 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4244 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4245 key, g_hash_table_size(session->unconfirmed_messages));
4246 g_free(key);
4248 sipe_send_message(sip, dialog, msg->body, msg->content_type);
4249 } SIPE_DIALOG_FOREACH_END;
4251 entry2 = sipe_session_dequeue_message(session);
4255 static void
4256 sipe_refer_notify(struct sipe_account_data *sip,
4257 struct sip_session *session,
4258 const gchar *who,
4259 int status,
4260 const gchar *desc)
4262 gchar *hdr;
4263 gchar *body;
4264 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4266 hdr = g_strdup_printf(
4267 "Event: refer\r\n"
4268 "Subscription-State: %s\r\n"
4269 "Content-Type: message/sipfrag\r\n",
4270 status >= 200 ? "terminated" : "active");
4272 body = g_strdup_printf(
4273 "SIP/2.0 %d %s\r\n",
4274 status, desc);
4276 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
4278 g_free(hdr);
4279 g_free(body);
4282 static gboolean
4283 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
4285 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
4286 struct sip_session *session;
4287 struct sip_dialog *dialog;
4288 char *cseq;
4289 char *key;
4290 struct queued_message *message;
4291 struct sipmsg *request_msg = trans->msg;
4293 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4294 gchar *referred_by;
4296 session = sipe_session_find_chat_by_callid(sip, callid);
4297 if (!session) {
4298 session = sipe_session_find_im(sip, with);
4300 if (!session) {
4301 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4302 g_free(with);
4303 return FALSE;
4306 dialog = sipe_dialog_find(session, with);
4307 if (!dialog) {
4308 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4309 g_free(with);
4310 return FALSE;
4313 sipe_dialog_parse(dialog, msg, TRUE);
4315 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
4316 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
4317 g_free(cseq);
4318 message = g_hash_table_lookup(session->unconfirmed_messages, key);
4320 if (msg->response != 200) {
4321 PurpleBuddy *pbuddy;
4322 const char *alias = with;
4323 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
4324 int warning = -1;
4326 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4328 if (warn_hdr) {
4329 gchar **parts = g_strsplit(warn_hdr, " ", 2);
4330 if (parts[0]) {
4331 warning = atoi(parts[0]);
4333 g_strfreev(parts);
4336 /* cancel file transfer as rejected by server */
4337 if (msg->response == 606 && /* Not acceptable all. */
4338 warning == 309 && /* Message contents not allowed by policy */
4339 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
4341 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
4342 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4343 sipe_utils_nameval_free(parsed_body);
4346 if ((pbuddy = purple_find_buddy(sip->account, with))) {
4347 alias = purple_buddy_get_alias(pbuddy);
4350 if (message) {
4351 sipe_present_message_undelivered_err(sip, session, msg->response, warning, alias, message->body);
4352 } else {
4353 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
4354 sipe_present_err(sip, session, tmp_msg);
4355 g_free(tmp_msg);
4358 sipe_dialog_remove(session, with);
4360 g_free(key);
4361 g_free(with);
4362 return FALSE;
4365 dialog->cseq = 0;
4366 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4367 dialog->outgoing_invite = NULL;
4368 dialog->is_established = TRUE;
4370 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
4371 if (referred_by) {
4372 sipe_refer_notify(sip, session, referred_by, 200, "OK");
4373 g_free(referred_by);
4376 /* add user to chat if it is a multiparty session */
4377 if (session->is_multiparty) {
4378 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4379 with, NULL,
4380 PURPLE_CBFLAGS_NONE, TRUE);
4383 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
4384 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4385 sipe_session_dequeue_message(session);
4388 sipe_im_process_queue(sip, session);
4390 g_hash_table_remove(session->unconfirmed_messages, key);
4391 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4392 key, g_hash_table_size(session->unconfirmed_messages));
4394 g_free(key);
4395 g_free(with);
4396 return TRUE;
4400 void
4401 sipe_invite(struct sipe_account_data *sip,
4402 struct sip_session *session,
4403 const gchar *who,
4404 const gchar *msg_body,
4405 const gchar *msg_content_type,
4406 const gchar *referred_by,
4407 const gboolean is_triggered)
4409 gchar *hdr;
4410 gchar *to;
4411 gchar *contact;
4412 gchar *body;
4413 gchar *self;
4414 char *ms_text_format = NULL;
4415 gchar *roster_manager;
4416 gchar *end_points;
4417 gchar *referred_by_str;
4418 struct sip_dialog *dialog = sipe_dialog_find(session, who);
4420 if (dialog && dialog->is_established) {
4421 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
4422 return;
4425 if (!dialog) {
4426 dialog = sipe_dialog_add(session);
4427 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
4428 dialog->with = g_strdup(who);
4431 if (!(dialog->ourtag)) {
4432 dialog->ourtag = gentag();
4435 to = sip_uri(who);
4437 if (msg_body) {
4438 char *msgtext = NULL;
4439 char *base64_msg;
4440 const gchar *msgr = "";
4441 char *key;
4442 struct queued_message *message;
4443 gchar *tmp = NULL;
4445 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
4446 char *msgformat;
4447 gchar *msgr_value;
4449 sipe_parse_html(msg_body, &msgformat, &msgtext);
4450 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
4452 msgr_value = sipmsg_get_msgr_string(msgformat);
4453 g_free(msgformat);
4454 if (msgr_value) {
4455 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
4456 g_free(msgr_value);
4458 } else {
4459 msgtext = g_strdup(msg_body);
4462 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
4463 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
4464 msg_content_type ? msg_content_type : "text/plain",
4465 msgr,
4466 base64_msg);
4467 g_free(msgtext);
4468 g_free(tmp);
4469 g_free(base64_msg);
4471 message = g_new0(struct queued_message,1);
4472 message->body = g_strdup(msg_body);
4473 if (msg_content_type != NULL)
4474 message->content_type = g_strdup(msg_content_type);
4476 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4477 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4478 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4479 key, g_hash_table_size(session->unconfirmed_messages));
4480 g_free(key);
4483 contact = get_contact(sip);
4484 end_points = get_end_points(sip, session);
4485 self = sip_uri_self(sip);
4486 roster_manager = g_strdup_printf(
4487 "Roster-Manager: %s\r\n"
4488 "EndPoints: %s\r\n",
4489 self,
4490 end_points);
4491 referred_by_str = referred_by ?
4492 g_strdup_printf(
4493 "Referred-By: %s\r\n",
4494 referred_by)
4495 : g_strdup("");
4496 hdr = g_strdup_printf(
4497 "Supported: ms-sender\r\n"
4498 "%s"
4499 "%s"
4500 "%s"
4501 "%s"
4502 "Contact: %s\r\n%s"
4503 "Content-Type: application/sdp\r\n",
4504 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4505 referred_by_str,
4506 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4507 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4508 contact,
4509 ms_text_format ? ms_text_format : "");
4510 g_free(ms_text_format);
4511 g_free(self);
4513 body = g_strdup_printf(
4514 "v=0\r\n"
4515 "o=- 0 0 IN IP4 %s\r\n"
4516 "s=session\r\n"
4517 "c=IN IP4 %s\r\n"
4518 "t=0 0\r\n"
4519 "m=%s %d sip null\r\n"
4520 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4521 sipe_backend_network_ip_address(),
4522 sipe_backend_network_ip_address(),
4523 sip->ocs2007 ? "message" : "x-ms-message",
4524 SIP_TO_CORE_PRIVATE->server_port);
4526 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
4527 to, to, hdr, body, dialog, process_invite_response);
4529 g_free(to);
4530 g_free(roster_manager);
4531 g_free(end_points);
4532 g_free(referred_by_str);
4533 g_free(body);
4534 g_free(hdr);
4535 g_free(contact);
4538 static void
4539 sipe_refer(struct sipe_account_data *sip,
4540 struct sip_session *session,
4541 const gchar *who)
4543 gchar *hdr;
4544 gchar *contact;
4545 gchar *epid = get_epid(sip);
4546 struct sip_dialog *dialog = sipe_dialog_find(session,
4547 session->roster_manager);
4548 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4550 contact = get_contact(sip);
4551 hdr = g_strdup_printf(
4552 "Contact: %s\r\n"
4553 "Refer-to: <%s>\r\n"
4554 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4555 "Require: com.microsoft.rtc-multiparty\r\n",
4556 contact,
4557 who,
4558 sip->username,
4559 ourtag ? ";tag=" : "",
4560 ourtag ? ourtag : "",
4561 epid);
4562 g_free(epid);
4564 send_sip_request(sip->gc, "REFER",
4565 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4567 g_free(hdr);
4568 g_free(contact);
4571 static void
4572 sipe_send_election_request_rm(struct sipe_account_data *sip,
4573 struct sip_dialog *dialog,
4574 int bid)
4576 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4578 gchar *body = g_strdup_printf(
4579 "<?xml version=\"1.0\"?>\r\n"
4580 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4581 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4582 sip->username, bid);
4584 send_sip_request(sip->gc, "INFO",
4585 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4587 g_free(body);
4590 static void
4591 sipe_send_election_set_rm(struct sipe_account_data *sip,
4592 struct sip_dialog *dialog)
4594 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4596 gchar *body = g_strdup_printf(
4597 "<?xml version=\"1.0\"?>\r\n"
4598 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4599 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4600 sip->username);
4602 send_sip_request(sip->gc, "INFO",
4603 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4605 g_free(body);
4608 static void
4609 sipe_session_close(struct sipe_account_data *sip,
4610 struct sip_session * session)
4612 if (session && session->focus_uri) {
4613 sipe_conf_immcu_closed(sip, session);
4614 conf_session_close(sip, session);
4617 if (session) {
4618 SIPE_DIALOG_FOREACH {
4619 /* @TODO slow down BYE message sending rate */
4620 /* @see single subscription code */
4621 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4622 } SIPE_DIALOG_FOREACH_END;
4624 sipe_session_remove(sip, session);
4628 static void
4629 sipe_session_close_all(struct sipe_account_data *sip)
4631 GSList *entry;
4632 while ((entry = sip->sessions) != NULL) {
4633 sipe_session_close(sip, entry->data);
4637 void
4638 sipe_convo_closed(PurpleConnection * gc, const char *who)
4640 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
4642 SIPE_DEBUG_INFO("conversation with %s closed", who);
4643 sipe_session_close(sip, sipe_session_find_im(sip, who));
4646 void
4647 sipe_chat_leave (PurpleConnection *gc, int id)
4649 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
4650 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
4652 sipe_session_close(sip, session);
4655 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4656 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4658 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
4659 struct sip_session *session;
4660 struct sip_dialog *dialog;
4661 gchar *uri = sip_uri(who);
4663 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4665 session = sipe_session_find_or_add_im(sip, uri);
4666 dialog = sipe_dialog_find(session, uri);
4668 // Queue the message
4669 sipe_session_enqueue_message(session, what, NULL);
4671 if (dialog && !dialog->outgoing_invite) {
4672 sipe_im_process_queue(sip, session);
4673 } else if (!dialog || !dialog->outgoing_invite) {
4674 // Need to send the INVITE to get the outgoing dialog setup
4675 sipe_invite(sip, session, uri, what, NULL, NULL, FALSE);
4678 g_free(uri);
4679 return 1;
4682 int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4683 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4685 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
4686 struct sip_session *session;
4688 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
4690 session = sipe_session_find_chat_by_id(sip, id);
4692 // Queue the message
4693 if (session && session->dialogs) {
4694 sipe_session_enqueue_message(session,what,NULL);
4695 sipe_im_process_queue(sip, session);
4696 } else if (sip) {
4697 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4698 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4700 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
4701 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
4703 if (sip->ocs2007) {
4704 struct sip_session *session = sipe_session_add_chat(sip);
4706 session->is_multiparty = FALSE;
4707 session->focus_uri = g_strdup(proto_chat_id);
4708 sipe_session_enqueue_message(session, what, NULL);
4709 sipe_invite_conf_focus(sip, session);
4713 return 1;
4716 /* End IM Session (INVITE and MESSAGE methods) */
4718 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
4720 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4721 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4722 gchar *from;
4723 struct sip_session *session;
4725 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
4727 /* Call Control protocol */
4728 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4730 process_incoming_info_csta(sip, msg);
4731 return;
4734 from = parse_from(sipmsg_find_header(msg, "From"));
4735 session = sipe_session_find_chat_by_callid(sip, callid);
4736 if (!session) {
4737 session = sipe_session_find_im(sip, from);
4739 if (!session) {
4740 g_free(from);
4741 return;
4744 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4746 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4747 const sipe_xml *xn_request_rm = sipe_xml_child(xn_action, "RequestRM");
4748 const sipe_xml *xn_set_rm = sipe_xml_child(xn_action, "SetRM");
4750 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4752 if (xn_request_rm) {
4753 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
4754 int bid = sipe_xml_int_attribute(xn_request_rm, "bid", 0);
4755 gchar *body = g_strdup_printf(
4756 "<?xml version=\"1.0\"?>\r\n"
4757 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4758 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4759 sip->username,
4760 session->bid < bid ? "true" : "false");
4761 send_sip_response(sip->gc, msg, 200, "OK", body);
4762 g_free(body);
4763 } else if (xn_set_rm) {
4764 gchar *body;
4765 const char *rm = sipe_xml_attribute(xn_set_rm, "uri");
4766 g_free(session->roster_manager);
4767 session->roster_manager = g_strdup(rm);
4769 body = g_strdup_printf(
4770 "<?xml version=\"1.0\"?>\r\n"
4771 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4772 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4773 sip->username);
4774 send_sip_response(sip->gc, msg, 200, "OK", body);
4775 g_free(body);
4777 sipe_xml_free(xn_action);
4780 else
4782 /* looks like purple lacks typing notification for chat */
4783 if (!session->is_multiparty && !session->focus_uri) {
4784 sipe_xml *xn_keyboard_activity = sipe_xml_parse(msg->body, msg->bodylen);
4785 const char *status = sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity, "status"),
4786 "status");
4787 if (sipe_strequal(status, "type")) {
4788 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4789 } else if (sipe_strequal(status, "idle")) {
4790 serv_got_typing_stopped(sip->gc, from);
4792 sipe_xml_free(xn_keyboard_activity);
4795 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4797 g_free(from);
4800 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
4802 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4803 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4804 struct sip_session *session;
4805 struct sip_dialog *dialog;
4807 /* collect dialog identification
4808 * we need callid, ourtag and theirtag to unambiguously identify dialog
4810 /* take data before 'msg' will be modified by send_sip_response */
4811 dialog = g_new0(struct sip_dialog, 1);
4812 dialog->callid = g_strdup(callid);
4813 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4814 dialog->with = g_strdup(from);
4815 sipe_dialog_parse(dialog, msg, FALSE);
4817 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4819 session = sipe_session_find_chat_by_callid(sip, callid);
4820 if (!session) {
4821 session = sipe_session_find_im(sip, from);
4823 if (!session) {
4824 sipe_dialog_free(dialog);
4825 g_free(from);
4826 return;
4829 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4830 g_free(session->roster_manager);
4831 session->roster_manager = NULL;
4834 /* This what BYE is essentially for - terminating dialog */
4835 sipe_dialog_remove_3(session, dialog);
4836 sipe_dialog_free(dialog);
4837 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4838 sipe_conf_immcu_closed(sip, session);
4839 } else if (session->is_multiparty) {
4840 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4843 g_free(from);
4846 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
4848 gchar *self = sip_uri_self(sip);
4849 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4850 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4851 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4852 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4853 struct sip_session *session;
4854 struct sip_dialog *dialog;
4856 session = sipe_session_find_chat_by_callid(sip, callid);
4857 dialog = sipe_dialog_find(session, from);
4859 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
4860 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
4861 } else {
4862 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
4864 sipe_invite(sip, session, refer_to, NULL, NULL, referred_by, FALSE);
4867 g_free(self);
4868 g_free(from);
4869 g_free(refer_to);
4870 g_free(referred_by);
4873 unsigned int
4874 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4876 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
4877 struct sip_session *session;
4878 struct sip_dialog *dialog;
4880 if (state == PURPLE_NOT_TYPING)
4881 return 0;
4883 session = sipe_session_find_im(sip, who);
4884 dialog = sipe_dialog_find(session, who);
4886 if (session && dialog && dialog->is_established) {
4887 send_sip_request(gc, "INFO", who, who,
4888 "Content-Type: application/xml\r\n",
4889 SIPE_SEND_TYPING, dialog, NULL);
4891 return SIPE_TYPING_SEND_TIMEOUT;
4894 static void do_reauthenticate_cb(struct sipe_core_private *sipe_private,
4895 SIPE_UNUSED_PARAMETER void *unused)
4897 struct sipe_account_data *sip = sipe_private->temporary;
4898 /* register again when security token expires */
4899 /* we have to start a new authentication as the security token
4900 * is almost expired by sending a not signed REGISTER message */
4901 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
4902 sipe_auth_free(&sip->registrar);
4903 sipe_auth_free(&sip->proxy);
4904 sip->registerstatus = 0;
4905 do_register(sip);
4906 sip->reauthenticate_set = FALSE;
4909 static gboolean
4910 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data *sip,
4911 struct sipmsg *msg,
4912 GSList *parsed_body)
4914 gboolean found = FALSE;
4916 if (parsed_body) {
4917 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4919 if (sipe_strequal(invitation_command, "INVITE")) {
4920 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4921 found = TRUE;
4922 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4923 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4924 found = TRUE;
4925 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4926 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4927 found = TRUE;
4930 return found;
4933 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4935 gchar *from;
4936 const gchar *contenttype;
4937 gboolean found = FALSE;
4939 from = parse_from(sipmsg_find_header(msg, "From"));
4941 if (!from) return;
4943 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
4945 contenttype = sipmsg_find_header(msg, "Content-Type");
4946 if (g_str_has_prefix(contenttype, "text/plain")
4947 || g_str_has_prefix(contenttype, "text/html")
4948 || g_str_has_prefix(contenttype, "multipart/related")
4949 || g_str_has_prefix(contenttype, "multipart/alternative"))
4951 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4952 gchar *html = get_html_message(contenttype, msg->body);
4954 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4955 if (!session) {
4956 session = sipe_session_find_im(sip, from);
4959 if (session && session->focus_uri) { /* a conference */
4960 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4961 gchar *sender = parse_from(tmp);
4962 g_free(tmp);
4963 serv_got_chat_in(sip->gc, session->chat_id, sender,
4964 PURPLE_MESSAGE_RECV, html, time(NULL));
4965 g_free(sender);
4966 } else if (session && session->is_multiparty) { /* a multiparty chat */
4967 serv_got_chat_in(sip->gc, session->chat_id, from,
4968 PURPLE_MESSAGE_RECV, html, time(NULL));
4969 } else {
4970 serv_got_im(sip->gc, from, html, 0, time(NULL));
4972 g_free(html);
4973 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4974 found = TRUE;
4976 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
4977 sipe_xml *isc = sipe_xml_parse(msg->body, msg->bodylen);
4978 const sipe_xml *state;
4979 gchar *statedata;
4981 if (!isc) {
4982 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
4983 g_free(from);
4984 return;
4987 state = sipe_xml_child(isc, "state");
4989 if (!state) {
4990 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
4991 sipe_xml_free(isc);
4992 g_free(from);
4993 return;
4996 statedata = sipe_xml_data(state);
4997 if (statedata) {
4998 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4999 else serv_got_typing_stopped(sip->gc, from);
5001 g_free(statedata);
5003 sipe_xml_free(isc);
5004 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5005 found = TRUE;
5006 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
5007 GSList *body = sipe_ft_parse_msg_body(msg->body);
5008 found = sipe_process_incoming_x_msmsgsinvite(sip, msg, body);
5009 sipe_utils_nameval_free(body);
5010 if (found) {
5011 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5014 if (!found) {
5015 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5016 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
5017 if (!session) {
5018 session = sipe_session_find_im(sip, from);
5020 if (session) {
5021 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5022 from);
5023 sipe_present_err(sip, session, errmsg);
5024 g_free(errmsg);
5027 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
5028 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
5030 g_free(from);
5033 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
5035 gchar *body;
5036 gchar *newTag;
5037 const gchar *oldHeader;
5038 gchar *newHeader;
5039 gboolean is_multiparty = FALSE;
5040 gboolean is_triggered = FALSE;
5041 gboolean was_multiparty = TRUE;
5042 gboolean just_joined = FALSE;
5043 gchar *from;
5044 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
5045 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
5046 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
5047 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
5048 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5049 GSList *end_points = NULL;
5050 char *tmp = NULL;
5051 struct sip_session *session;
5052 const gchar *ms_text_format;
5054 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
5055 g_free(tmp);
5057 /* Invitation to join conference */
5058 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
5059 process_incoming_invite_conf(sip, msg);
5060 return;
5063 /* Only accept text invitations */
5064 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
5065 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5066 return;
5069 // TODO There *must* be a better way to clean up the To header to add a tag...
5070 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5071 oldHeader = sipmsg_find_header(msg, "To");
5072 newTag = gentag();
5073 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
5074 sipmsg_remove_header_now(msg, "To");
5075 sipmsg_add_header_now(msg, "To", newHeader);
5076 g_free(newHeader);
5078 if (end_points_hdr) {
5079 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
5081 if (g_slist_length(end_points) > 2) {
5082 is_multiparty = TRUE;
5085 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
5086 is_triggered = TRUE;
5087 is_multiparty = TRUE;
5090 session = sipe_session_find_chat_by_callid(sip, callid);
5091 /* Convert to multiparty */
5092 if (session && is_multiparty && !session->is_multiparty) {
5093 g_free(session->with);
5094 session->with = NULL;
5095 was_multiparty = FALSE;
5096 session->is_multiparty = TRUE;
5097 session->chat_id = rand();
5100 if (!session && is_multiparty) {
5101 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
5103 /* IM session */
5104 from = parse_from(sipmsg_find_header(msg, "From"));
5105 if (!session) {
5106 session = sipe_session_find_or_add_im(sip, from);
5109 if (session) {
5110 g_free(session->callid);
5111 session->callid = g_strdup(callid);
5113 session->is_multiparty = is_multiparty;
5114 if (roster_manager) {
5115 session->roster_manager = g_strdup(roster_manager);
5119 if (is_multiparty && end_points) {
5120 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
5121 GSList *entry = end_points;
5122 while (entry) {
5123 struct sip_dialog *dialog;
5124 struct sipendpoint *end_point = entry->data;
5125 entry = entry->next;
5127 if (!g_strcasecmp(from, end_point->contact) ||
5128 !g_strcasecmp(to, end_point->contact))
5129 continue;
5131 dialog = sipe_dialog_find(session, end_point->contact);
5132 if (dialog) {
5133 g_free(dialog->theirepid);
5134 dialog->theirepid = end_point->epid;
5135 end_point->epid = NULL;
5136 } else {
5137 dialog = sipe_dialog_add(session);
5139 dialog->callid = g_strdup(session->callid);
5140 dialog->with = end_point->contact;
5141 end_point->contact = NULL;
5142 dialog->theirepid = end_point->epid;
5143 end_point->epid = NULL;
5145 just_joined = TRUE;
5147 /* send triggered INVITE */
5148 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, TRUE);
5151 g_free(to);
5154 if (end_points) {
5155 GSList *entry = end_points;
5156 while (entry) {
5157 struct sipendpoint *end_point = entry->data;
5158 entry = entry->next;
5159 g_free(end_point->contact);
5160 g_free(end_point->epid);
5161 g_free(end_point);
5163 g_slist_free(end_points);
5166 if (session) {
5167 struct sip_dialog *dialog = sipe_dialog_find(session, from);
5168 if (dialog) {
5169 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5170 sipe_dialog_parse_routes(dialog, msg, FALSE);
5171 } else {
5172 dialog = sipe_dialog_add(session);
5174 dialog->callid = g_strdup(session->callid);
5175 dialog->with = g_strdup(from);
5176 sipe_dialog_parse(dialog, msg, FALSE);
5178 if (!dialog->ourtag) {
5179 dialog->ourtag = newTag;
5180 newTag = NULL;
5183 just_joined = TRUE;
5185 } else {
5186 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5188 g_free(newTag);
5190 if (is_multiparty && !session->conv) {
5191 gchar *chat_title = sipe_chat_get_name(callid);
5192 gchar *self = sip_uri_self(sip);
5193 /* create prpl chat */
5194 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
5195 session->chat_title = g_strdup(chat_title);
5196 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
5197 /* add self */
5198 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5199 self, NULL,
5200 PURPLE_CBFLAGS_NONE, FALSE);
5201 g_free(chat_title);
5202 g_free(self);
5205 if (is_multiparty && !was_multiparty) {
5206 /* add current IM counterparty to chat */
5207 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5208 sipe_dialog_first(session)->with, NULL,
5209 PURPLE_CBFLAGS_NONE, FALSE);
5212 /* add inviting party to chat */
5213 if (just_joined && session->conv) {
5214 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
5215 from, NULL,
5216 PURPLE_CBFLAGS_NONE, TRUE);
5219 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5221 /* This used only in 2005 official client, not 2007 or Reuters.
5222 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5223 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5225 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5226 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
5227 if (is_multiparty ||
5228 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
5230 if (ms_text_format) {
5231 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
5233 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
5234 if (tmp) {
5235 gsize len;
5236 gchar *body = (gchar *) g_base64_decode(tmp, &len);
5238 GSList *parsed_body = sipe_ft_parse_msg_body(body);
5240 sipe_process_incoming_x_msmsgsinvite(sip, msg, parsed_body);
5241 sipe_utils_nameval_free(parsed_body);
5242 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5244 g_free(tmp);
5246 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
5248 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5249 gchar *html = get_html_message(ms_text_format, NULL);
5250 if (html) {
5251 if (is_multiparty) {
5252 serv_got_chat_in(sip->gc, session->chat_id, from,
5253 PURPLE_MESSAGE_RECV, html, time(NULL));
5254 } else {
5255 serv_got_im(sip->gc, from, html, 0, time(NULL));
5257 g_free(html);
5258 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
5264 g_free(from);
5266 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
5267 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5268 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5270 body = g_strdup_printf(
5271 "v=0\r\n"
5272 "o=- 0 0 IN IP4 %s\r\n"
5273 "s=session\r\n"
5274 "c=IN IP4 %s\r\n"
5275 "t=0 0\r\n"
5276 "m=%s %d sip sip:%s\r\n"
5277 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5278 sipe_backend_network_ip_address(),
5279 sipe_backend_network_ip_address(),
5280 sip->ocs2007 ? "message" : "x-ms-message",
5281 SIP_TO_CORE_PRIVATE->server_port,
5282 sip->username);
5283 send_sip_response(sip->gc, msg, 200, "OK", body);
5284 g_free(body);
5287 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
5289 gchar *body;
5291 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5292 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
5293 sipmsg_add_header(msg, "Content-Type", "application/sdp");
5295 body = g_strdup_printf(
5296 "v=0\r\n"
5297 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5298 "s=session\r\n"
5299 "c=IN IP4 0.0.0.0\r\n"
5300 "t=0 0\r\n"
5301 "m=%s %d sip sip:%s\r\n"
5302 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
5303 sip->ocs2007 ? "message" : "x-ms-message",
5304 SIP_TO_CORE_PRIVATE->server_port,
5305 sip->username);
5306 send_sip_response(sip->gc, msg, 200, "OK", body);
5307 g_free(body);
5310 static const char*
5311 sipe_get_auth_scheme_name(struct sipe_account_data *sip)
5313 const char *res = "NTLM";
5314 #ifdef HAVE_LIBKRB5
5315 if (purple_account_get_bool(sip->account, "krb5", FALSE)) {
5316 res = "Kerberos";
5318 #else
5319 (void) sip; /* make compiler happy */
5320 #endif
5321 return res;
5324 /* server_name must be g_alloc()'ed */
5325 static void sipe_server_register(struct sipe_core_private *sipe_private,
5326 guint type,
5327 gchar *server_name,
5328 guint server_port)
5330 sipe_private->transport_type = type;
5331 sipe_private->server_name = server_name;
5332 sipe_private->server_port =
5333 (server_port != 0) ? server_port :
5334 (type == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5336 sipe_private->public.transport = sipe_backend_transport_sip_connect(SIPE_CORE_PUBLIC,
5337 type,
5338 server_name,
5339 sipe_private->server_port);
5340 if (sipe_private->public.transport) {
5341 sipe_private->public.transport->user_data = sipe_private;
5345 static void sipe_connection_cleanup(struct sipe_account_data *);
5347 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
5348 SIPE_UNUSED_PARAMETER struct transaction *trans)
5350 struct sipe_core_private *sipe_private = SIP_TO_CORE_PRIVATE;
5351 gchar *tmp;
5352 const gchar *expires_header;
5353 int expires, i;
5354 GSList *hdr = msg->headers;
5355 struct sipnameval *elem;
5357 expires_header = sipmsg_find_header(msg, "Expires");
5358 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
5359 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
5361 switch (msg->response) {
5362 case 200:
5363 if (expires == 0) {
5364 sip->registerstatus = 0;
5365 } else {
5366 const gchar *contact_hdr;
5367 gchar *gruu = NULL;
5368 gchar *epid;
5369 gchar *uuid;
5370 gchar *timeout;
5371 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
5372 const char *auth_scheme;
5374 if (!sip->reregister_set) {
5375 gchar *action_name = g_strdup_printf("<%s>", "registration");
5376 sipe_schedule_action(action_name,
5377 expires,
5378 do_register_cb,
5379 NULL,
5380 sipe_private,
5381 NULL);
5382 g_free(action_name);
5383 sip->reregister_set = TRUE;
5386 sip->registerstatus = 3;
5388 if (server_hdr && !sip->server_version) {
5389 sip->server_version = g_strdup(server_hdr);
5390 g_free(default_ua);
5391 default_ua = NULL;
5394 auth_scheme = sipe_get_auth_scheme_name(sip);
5395 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5397 if (tmp) {
5398 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
5399 fill_auth(tmp, &sip->registrar);
5402 if (!sip->reauthenticate_set) {
5403 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
5404 guint reauth_timeout;
5405 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
5406 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5407 reauth_timeout = sip->registrar.expires - 300;
5408 } else {
5409 /* NTLM: we have to reauthenticate as our security token expires
5410 after eight hours (be five minutes early) */
5411 reauth_timeout = (8 * 3600) - 300;
5413 sipe_schedule_action(action_name,
5414 reauth_timeout,
5415 do_reauthenticate_cb,
5416 NULL,
5417 sipe_private,
5418 NULL);
5419 g_free(action_name);
5420 sip->reauthenticate_set = TRUE;
5423 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5425 epid = get_epid(sip);
5426 uuid = generateUUIDfromEPID(epid);
5427 g_free(epid);
5429 // There can be multiple Contact headers (one per location where the user is logged in) so
5430 // make sure to only get the one for this uuid
5431 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5432 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5433 if (valid_contact) {
5434 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5435 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5436 g_free(valid_contact);
5437 break;
5438 } else {
5439 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5442 g_free(uuid);
5444 g_free(sip->contact);
5445 if(gruu) {
5446 sip->contact = g_strdup_printf("<%s>", gruu);
5447 g_free(gruu);
5448 } else {
5449 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5450 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace",
5451 sip->username,
5452 sipe_private->public.transport->client_port,
5453 sipe_backend_network_ip_address(),
5454 TRANSPORT_DESCRIPTOR);
5456 sip->ocs2007 = FALSE;
5457 sip->batched_support = FALSE;
5459 while(hdr)
5461 elem = hdr->data;
5462 if (sipe_strcase_equal(elem->name, "Supported")) {
5463 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5464 /* We interpret this as OCS2007+ indicator */
5465 sip->ocs2007 = TRUE;
5466 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5468 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5469 sip->batched_support = TRUE;
5470 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5473 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5474 gchar **caps = g_strsplit(elem->value,",",0);
5475 i = 0;
5476 while (caps[i]) {
5477 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5478 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5479 i++;
5481 g_strfreev(caps);
5483 hdr = g_slist_next(hdr);
5486 /* rejoin open chats to be able to use them by continue to send messages */
5487 purple_conversation_foreach(sipe_rejoin_chat);
5489 /* subscriptions */
5490 if (!sip->subscribed) { //do it just once, not every re-register
5492 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5493 (GCompareFunc)g_ascii_strcasecmp)) {
5494 sipe_subscribe_roaming_contacts(sip);
5497 /* For 2007+ it does not make sence to subscribe to:
5498 * vnd-microsoft-roaming-ACL
5499 * vnd-microsoft-provisioning (not v2)
5500 * presence.wpending
5501 * These are for backward compatibility.
5503 if (sip->ocs2007)
5505 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5506 (GCompareFunc)g_ascii_strcasecmp)) {
5507 sipe_subscribe_roaming_self(sip);
5509 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5510 (GCompareFunc)g_ascii_strcasecmp)) {
5511 sipe_subscribe_roaming_provisioning_v2(sip);
5514 /* For 2005- servers */
5515 else
5517 //sipe_options_request(sip, sipe_private->public.sip_domain);
5519 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5520 (GCompareFunc)g_ascii_strcasecmp)) {
5521 sipe_subscribe_roaming_acl(sip);
5523 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5524 (GCompareFunc)g_ascii_strcasecmp)) {
5525 sipe_subscribe_roaming_provisioning(sip);
5527 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5528 (GCompareFunc)g_ascii_strcasecmp)) {
5529 sipe_subscribe_presence_wpending(sipe_private,
5530 msg);
5533 /* For 2007+ we publish our initial statuses and calendar data only after
5534 * received our existing publications in sipe_process_roaming_self()
5535 * Only in this case we know versions of current publications made
5536 * on our behalf.
5538 /* For 2005- we publish our initial statuses only after
5539 * received our existing UserInfo data in response to
5540 * self subscription.
5541 * Only in this case we won't override existing UserInfo data
5542 * set earlier or by other client on our behalf.
5546 sip->subscribed = TRUE;
5549 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5550 "timeout=", ";", NULL);
5551 if (timeout != NULL) {
5552 sscanf(timeout, "%u", &sipe_private->public.keepalive_timeout);
5553 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5554 sipe_private->public.keepalive_timeout);
5555 g_free(timeout);
5558 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5560 break;
5561 case 301:
5563 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5565 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5566 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5567 gchar **tmp;
5568 gchar *hostname;
5569 int port = 0;
5570 guint transport = SIPE_TRANSPORT_TLS;
5571 int i = 1;
5573 tmp = g_strsplit(parts[0], ":", 0);
5574 hostname = g_strdup(tmp[0]);
5575 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5576 g_strfreev(tmp);
5578 while (parts[i]) {
5579 tmp = g_strsplit(parts[i], "=", 0);
5580 if (tmp[1]) {
5581 if (g_strcasecmp("transport", tmp[0]) == 0) {
5582 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5583 transport = SIPE_TRANSPORT_TCP;
5587 g_strfreev(tmp);
5588 i++;
5590 g_strfreev(parts);
5592 /* Close old connection */
5593 sipe_connection_cleanup(sip);
5595 /* Create new connection */
5596 sipe_server_register(sipe_private, transport, hostname, port);
5597 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5598 hostname, port, TRANSPORT_DESCRIPTOR);
5600 g_free(redirect);
5602 break;
5603 case 401:
5604 if (sip->registerstatus != 2) {
5605 const char *auth_scheme;
5606 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5607 if (sip->registrar.retries > 3) {
5608 purple_connection_error_reason(sip->gc,
5609 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
5610 _("Authentication failed"));
5611 return TRUE;
5614 auth_scheme = sipe_get_auth_scheme_name(sip);
5615 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5617 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5618 if (!tmp) {
5619 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5620 purple_connection_error_reason(sip->gc,
5621 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
5622 tmp2);
5623 g_free(tmp2);
5624 return TRUE;
5626 fill_auth(tmp, &sip->registrar);
5627 sip->registerstatus = 2;
5628 if (sip->account->disconnecting) {
5629 do_register_exp(sip, 0);
5630 } else {
5631 do_register(sip);
5634 break;
5635 case 403:
5637 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5638 gchar **reason = NULL;
5639 gchar *warning;
5640 if (diagnostics != NULL) {
5641 /* Example header:
5642 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5644 reason = g_strsplit(diagnostics, "\"", 0);
5646 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5647 (reason && reason[1]) ? reason[1] : _("no reason given"));
5648 g_strfreev(reason);
5650 purple_connection_error_reason(sip->gc,
5651 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
5652 warning);
5653 g_free(warning);
5654 return TRUE;
5656 break;
5657 case 404:
5659 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5660 gchar *reason = NULL;
5661 gchar *warning;
5662 if (diagnostics != NULL) {
5663 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5665 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5666 diagnostics ? (reason ? reason : _("no reason given")) :
5667 _("SIP is either not enabled for the destination URI or it does not exist"));
5668 g_free(reason);
5670 purple_connection_error_reason(sip->gc,
5671 PURPLE_CONNECTION_ERROR_INVALID_USERNAME,
5672 warning);
5673 g_free(warning);
5674 return TRUE;
5676 break;
5677 case 503:
5678 case 504: /* Server time-out */
5680 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5681 gchar *reason = NULL;
5682 gchar *warning;
5683 if (diagnostics != NULL) {
5684 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5686 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5687 g_free(reason);
5689 purple_connection_error_reason(sip->gc,
5690 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
5691 warning);
5692 g_free(warning);
5693 return TRUE;
5695 break;
5697 return TRUE;
5701 * Returns 2005-style activity and Availability.
5703 * @param status Sipe statis id.
5705 static void
5706 sipe_get_act_avail_by_status_2005(const char *status,
5707 int *activity,
5708 int *availability)
5710 int avail = 300; /* online */
5711 int act = 400; /* Available */
5713 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5714 act = 100;
5715 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5716 // act = 150;
5717 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5718 act = 300;
5719 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5720 act = 400;
5721 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5722 // act = 500;
5723 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5724 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5725 act = 600;
5726 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5727 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5728 avail = 0; /* offline */
5729 act = 100;
5730 } else {
5731 act = 400; /* Available */
5734 if (activity) *activity = act;
5735 if (availability) *availability = avail;
5739 * [MS-SIP] 2.2.1
5741 * @param activity 2005 aggregated activity. Ex.: 600
5742 * @param availablity 2005 aggregated availablity. Ex.: 300
5744 static const char *
5745 sipe_get_status_by_act_avail_2005(const int activity,
5746 const int availablity,
5747 char **activity_desc)
5749 const char *status_id = NULL;
5750 const char *act = NULL;
5752 if (activity < 150) {
5753 status_id = SIPE_STATUS_ID_AWAY;
5754 } else if (activity < 200) {
5755 //status_id = SIPE_STATUS_ID_LUNCH;
5756 status_id = SIPE_STATUS_ID_AWAY;
5757 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5758 } else if (activity < 300) {
5759 //status_id = SIPE_STATUS_ID_IDLE;
5760 status_id = SIPE_STATUS_ID_AWAY;
5761 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5762 } else if (activity < 400) {
5763 status_id = SIPE_STATUS_ID_BRB;
5764 } else if (activity < 500) {
5765 status_id = SIPE_STATUS_ID_AVAILABLE;
5766 } else if (activity < 600) {
5767 //status_id = SIPE_STATUS_ID_ON_PHONE;
5768 status_id = SIPE_STATUS_ID_BUSY;
5769 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5770 } else if (activity < 700) {
5771 status_id = SIPE_STATUS_ID_BUSY;
5772 } else if (activity < 800) {
5773 status_id = SIPE_STATUS_ID_AWAY;
5774 } else {
5775 status_id = SIPE_STATUS_ID_AVAILABLE;
5778 if (availablity < 100)
5779 status_id = SIPE_STATUS_ID_OFFLINE;
5781 if (activity_desc && act) {
5782 g_free(*activity_desc);
5783 *activity_desc = g_strdup(act);
5786 return status_id;
5790 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5792 static const char*
5793 sipe_get_status_by_availability(int avail,
5794 char** activity_desc)
5796 const char *status;
5797 const char *act = NULL;
5799 if (avail < 3000) {
5800 status = SIPE_STATUS_ID_OFFLINE;
5801 } else if (avail < 4500) {
5802 status = SIPE_STATUS_ID_AVAILABLE;
5803 } else if (avail < 6000) {
5804 //status = SIPE_STATUS_ID_IDLE;
5805 status = SIPE_STATUS_ID_AVAILABLE;
5806 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5807 } else if (avail < 7500) {
5808 status = SIPE_STATUS_ID_BUSY;
5809 } else if (avail < 9000) {
5810 //status = SIPE_STATUS_ID_BUSYIDLE;
5811 status = SIPE_STATUS_ID_BUSY;
5812 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5813 } else if (avail < 12000) {
5814 status = SIPE_STATUS_ID_DND;
5815 } else if (avail < 15000) {
5816 status = SIPE_STATUS_ID_BRB;
5817 } else if (avail < 18000) {
5818 status = SIPE_STATUS_ID_AWAY;
5819 } else {
5820 status = SIPE_STATUS_ID_OFFLINE;
5823 if (activity_desc && act) {
5824 g_free(*activity_desc);
5825 *activity_desc = g_strdup(act);
5828 return status;
5832 * Returns 2007-style availability value
5834 * @param sipe_status_id (in)
5835 * @param activity_token (out) Must be g_free()'d after use if consumed.
5837 static int
5838 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5840 int availability;
5841 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5843 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5844 availability = 15500;
5845 if (!activity_token || !(*activity_token)) {
5846 activity = SIPE_ACTIVITY_AWAY;
5848 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5849 availability = 12500;
5850 activity = SIPE_ACTIVITY_BRB;
5851 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5852 availability = 9500;
5853 activity = SIPE_ACTIVITY_DND;
5854 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5855 availability = 6500;
5856 if (!activity_token || !(*activity_token)) {
5857 activity = SIPE_ACTIVITY_BUSY;
5859 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5860 availability = 3500;
5861 activity = SIPE_ACTIVITY_ONLINE;
5862 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5863 availability = 0;
5864 } else {
5865 // Offline or invisible
5866 availability = 18500;
5867 activity = SIPE_ACTIVITY_OFFLINE;
5870 if (activity_token) {
5871 *activity_token = g_strdup(sipe_activity_map[activity].token);
5873 return availability;
5876 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
5878 const char *uri;
5879 sipe_xml *xn_categories;
5880 const sipe_xml *xn_category;
5881 const char *status = NULL;
5882 gboolean do_update_status = FALSE;
5883 gboolean has_note_cleaned = FALSE;
5884 gboolean has_free_busy_cleaned = FALSE;
5886 xn_categories = sipe_xml_parse(data, len);
5887 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
5889 for (xn_category = sipe_xml_child(xn_categories, "category");
5890 xn_category ;
5891 xn_category = sipe_xml_twin(xn_category) )
5893 const sipe_xml *xn_node;
5894 const char *tmp;
5895 const char *attrVar = sipe_xml_attribute(xn_category, "name");
5896 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
5897 sipe_utils_str_to_time(tmp) : 0;
5899 /* contactCard */
5900 if (sipe_strequal(attrVar, "contactCard"))
5902 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
5904 if (card) {
5905 const sipe_xml *node;
5906 /* identity - Display Name and email */
5907 node = sipe_xml_child(card, "identity");
5908 if (node) {
5909 char* display_name = sipe_xml_data(
5910 sipe_xml_child(node, "name/displayName"));
5911 char* email = sipe_xml_data(
5912 sipe_xml_child(node, "email"));
5914 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5915 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5917 g_free(display_name);
5918 g_free(email);
5920 /* company */
5921 node = sipe_xml_child(card, "company");
5922 if (node) {
5923 char* company = sipe_xml_data(node);
5924 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
5925 g_free(company);
5927 /* department */
5928 node = sipe_xml_child(card, "department");
5929 if (node) {
5930 char* department = sipe_xml_data(node);
5931 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
5932 g_free(department);
5934 /* title */
5935 node = sipe_xml_child(card, "title");
5936 if (node) {
5937 char* title = sipe_xml_data(node);
5938 sipe_update_user_info(sip, uri, TITLE_PROP, title);
5939 g_free(title);
5941 /* office */
5942 node = sipe_xml_child(card, "office");
5943 if (node) {
5944 char* office = sipe_xml_data(node);
5945 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
5946 g_free(office);
5948 /* site (url) */
5949 node = sipe_xml_child(card, "url");
5950 if (node) {
5951 char* site = sipe_xml_data(node);
5952 sipe_update_user_info(sip, uri, SITE_PROP, site);
5953 g_free(site);
5955 /* phone */
5956 for (node = sipe_xml_child(card, "phone");
5957 node;
5958 node = sipe_xml_twin(node))
5960 const char *phone_type = sipe_xml_attribute(node, "type");
5961 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
5962 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
5964 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
5966 g_free(phone);
5967 g_free(phone_display_string);
5969 /* address */
5970 for (node = sipe_xml_child(card, "address");
5971 node;
5972 node = sipe_xml_twin(node))
5974 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
5975 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
5976 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
5977 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
5978 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
5979 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
5981 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
5982 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
5983 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
5984 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5985 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5987 g_free(street);
5988 g_free(city);
5989 g_free(state);
5990 g_free(zipcode);
5991 g_free(country_code);
5993 break;
5998 /* note */
5999 else if (sipe_strequal(attrVar, "note"))
6001 if (uri) {
6002 struct sipe_buddy *sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri);
6004 if (!has_note_cleaned) {
6005 has_note_cleaned = TRUE;
6007 g_free(sbuddy->note);
6008 sbuddy->note = NULL;
6009 sbuddy->is_oof_note = FALSE;
6010 sbuddy->note_since = publish_time;
6012 do_update_status = TRUE;
6014 if (sbuddy && (publish_time >= sbuddy->note_since)) {
6015 /* clean up in case no 'note' element is supplied
6016 * which indicate note removal in client
6018 g_free(sbuddy->note);
6019 sbuddy->note = NULL;
6020 sbuddy->is_oof_note = FALSE;
6021 sbuddy->note_since = publish_time;
6023 xn_node = sipe_xml_child(xn_category, "note/body");
6024 if (xn_node) {
6025 char *tmp;
6026 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
6027 g_free(tmp);
6028 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
6029 sbuddy->note_since = publish_time;
6031 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6032 uri, sbuddy->note ? sbuddy->note : "");
6034 /* to trigger UI refresh in case no status info is supplied in this update */
6035 do_update_status = TRUE;
6039 /* state */
6040 else if(sipe_strequal(attrVar, "state"))
6042 char *tmp;
6043 int availability;
6044 const sipe_xml *xn_availability;
6045 const sipe_xml *xn_activity;
6046 const sipe_xml *xn_meeting_subject;
6047 const sipe_xml *xn_meeting_location;
6048 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri) : NULL;
6050 xn_node = sipe_xml_child(xn_category, "state");
6051 if (!xn_node) continue;
6052 xn_availability = sipe_xml_child(xn_node, "availability");
6053 if (!xn_availability) continue;
6054 xn_activity = sipe_xml_child(xn_node, "activity");
6055 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
6056 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
6058 tmp = sipe_xml_data(xn_availability);
6059 availability = atoi(tmp);
6060 g_free(tmp);
6062 /* activity, meeting_subject, meeting_location */
6063 if (sbuddy) {
6064 char *tmp = NULL;
6066 /* activity */
6067 g_free(sbuddy->activity);
6068 sbuddy->activity = NULL;
6069 if (xn_activity) {
6070 const char *token = sipe_xml_attribute(xn_activity, "token");
6071 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
6073 /* from token */
6074 if (!is_empty(token)) {
6075 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
6077 /* from custom element */
6078 if (xn_custom) {
6079 char *custom = sipe_xml_data(xn_custom);
6081 if (!is_empty(custom)) {
6082 sbuddy->activity = custom;
6083 custom = NULL;
6085 g_free(custom);
6088 /* meeting_subject */
6089 g_free(sbuddy->meeting_subject);
6090 sbuddy->meeting_subject = NULL;
6091 if (xn_meeting_subject) {
6092 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
6094 if (!is_empty(meeting_subject)) {
6095 sbuddy->meeting_subject = meeting_subject;
6096 meeting_subject = NULL;
6098 g_free(meeting_subject);
6100 /* meeting_location */
6101 g_free(sbuddy->meeting_location);
6102 sbuddy->meeting_location = NULL;
6103 if (xn_meeting_location) {
6104 char *meeting_location = sipe_xml_data(xn_meeting_location);
6106 if (!is_empty(meeting_location)) {
6107 sbuddy->meeting_location = meeting_location;
6108 meeting_location = NULL;
6110 g_free(meeting_location);
6113 status = sipe_get_status_by_availability(availability, &tmp);
6114 if (sbuddy->activity && tmp) {
6115 char *tmp2 = sbuddy->activity;
6117 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
6118 g_free(tmp);
6119 g_free(tmp2);
6120 } else if (tmp) {
6121 sbuddy->activity = tmp;
6125 do_update_status = TRUE;
6127 /* calendarData */
6128 else if(sipe_strequal(attrVar, "calendarData"))
6130 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri) : NULL;
6131 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
6132 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
6134 if (sbuddy && xn_free_busy) {
6135 if (!has_free_busy_cleaned) {
6136 has_free_busy_cleaned = TRUE;
6138 g_free(sbuddy->cal_start_time);
6139 sbuddy->cal_start_time = NULL;
6141 g_free(sbuddy->cal_free_busy_base64);
6142 sbuddy->cal_free_busy_base64 = NULL;
6144 g_free(sbuddy->cal_free_busy);
6145 sbuddy->cal_free_busy = NULL;
6147 sbuddy->cal_free_busy_published = publish_time;
6150 if (publish_time >= sbuddy->cal_free_busy_published) {
6151 g_free(sbuddy->cal_start_time);
6152 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
6154 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
6155 15 : 0;
6157 g_free(sbuddy->cal_free_busy_base64);
6158 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
6160 g_free(sbuddy->cal_free_busy);
6161 sbuddy->cal_free_busy = NULL;
6163 sbuddy->cal_free_busy_published = publish_time;
6165 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);
6169 if (sbuddy && xn_working_hours) {
6170 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
6175 if (do_update_status) {
6176 if (!status) { /* no status category in this update, using contact's current status */
6177 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6178 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
6179 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
6180 status = purple_status_get_id(pstatus);
6183 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
6184 sipe_got_user_status(sip, uri, status);
6187 sipe_xml_free(xn_categories);
6190 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
6192 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6193 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
6194 payload->host = g_strdup(host);
6195 payload->buddies = server;
6196 sipe_subscribe_presence_batched_routed(SIP_TO_CORE_PRIVATE,
6197 payload);
6198 sipe_subscribe_presence_batched_routed_free(payload);
6201 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
6203 sipe_xml *xn_list;
6204 const sipe_xml *xn_resource;
6205 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
6206 g_free, NULL);
6207 GSList *server;
6208 gchar *host;
6210 xn_list = sipe_xml_parse(data, len);
6212 for (xn_resource = sipe_xml_child(xn_list, "resource");
6213 xn_resource;
6214 xn_resource = sipe_xml_twin(xn_resource) )
6216 const char *uri, *state;
6217 const sipe_xml *xn_instance;
6219 xn_instance = sipe_xml_child(xn_resource, "instance");
6220 if (!xn_instance) continue;
6222 uri = sipe_xml_attribute(xn_resource, "uri");
6223 state = sipe_xml_attribute(xn_instance, "state");
6224 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
6226 if (strstr(state, "resubscribe")) {
6227 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
6229 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6230 gchar *user = g_strdup(uri);
6231 host = g_strdup(poolFqdn);
6232 server = g_hash_table_lookup(servers, host);
6233 server = g_slist_append(server, user);
6234 g_hash_table_insert(servers, host, server);
6235 } else {
6236 sipe_subscribe_presence_single(SIP_TO_CORE_PRIVATE,
6237 (void *) uri);
6242 /* Send out any deferred poolFqdn subscriptions */
6243 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
6244 g_hash_table_destroy(servers);
6246 sipe_xml_free(xn_list);
6249 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
6251 gchar *uri;
6252 gchar *getbasic;
6253 gchar *activity = NULL;
6254 sipe_xml *pidf;
6255 const sipe_xml *basicstatus = NULL, *tuple, *status;
6256 gboolean isonline = FALSE;
6257 const sipe_xml *display_name_node;
6259 pidf = sipe_xml_parse(data, len);
6260 if (!pidf) {
6261 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
6262 return;
6265 if ((tuple = sipe_xml_child(pidf, "tuple")))
6267 if ((status = sipe_xml_child(tuple, "status"))) {
6268 basicstatus = sipe_xml_child(status, "basic");
6272 if (!basicstatus) {
6273 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6274 sipe_xml_free(pidf);
6275 return;
6278 getbasic = sipe_xml_data(basicstatus);
6279 if (!getbasic) {
6280 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6281 sipe_xml_free(pidf);
6282 return;
6285 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
6286 if (strstr(getbasic, "open")) {
6287 isonline = TRUE;
6289 g_free(getbasic);
6291 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6293 display_name_node = sipe_xml_child(pidf, "display-name");
6294 if (display_name_node) {
6295 char * display_name = sipe_xml_data(display_name_node);
6297 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6298 g_free(display_name);
6301 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
6302 if ((status = sipe_xml_child(tuple, "status"))) {
6303 if ((basicstatus = sipe_xml_child(status, "activities"))) {
6304 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
6305 activity = sipe_xml_data(basicstatus);
6306 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
6312 if (isonline) {
6313 const gchar * status_id = NULL;
6314 if (activity) {
6315 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
6316 status_id = SIPE_STATUS_ID_BUSY;
6317 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
6318 status_id = SIPE_STATUS_ID_AWAY;
6322 if (!status_id) {
6323 status_id = SIPE_STATUS_ID_AVAILABLE;
6326 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
6327 sipe_got_user_status(sip, uri, status_id);
6328 } else {
6329 sipe_got_user_status(sip, uri, SIPE_STATUS_ID_OFFLINE);
6332 g_free(activity);
6333 g_free(uri);
6334 sipe_xml_free(pidf);
6337 /** 2005 */
6338 static void
6339 sipe_user_info_has_updated(struct sipe_account_data *sip,
6340 const sipe_xml *xn_userinfo)
6342 const sipe_xml *xn_states;
6344 g_free(sip->user_states);
6345 sip->user_states = NULL;
6346 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
6347 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
6349 /* this is a hack-around to remove added newline after inner element,
6350 * state in this case, where it shouldn't be.
6351 * After several use of sipe_xml_stringify, amount of added newlines
6352 * grows significantly.
6354 if (orig) {
6355 gchar c, *stripped = orig;
6356 while ((c = *orig++)) {
6357 if ((c != '\n') /* && (c != '\r') */) {
6358 *stripped++ = c;
6361 *stripped = '\0';
6365 /* Publish initial state if not yet.
6366 * Assuming this happens on initial responce to self subscription
6367 * so we've already updated our UserInfo.
6369 if (!sip->initial_state_published) {
6370 send_presence_soap(sip, FALSE);
6371 /* dalayed run */
6372 sipe_schedule_action("<+update-calendar>",
6373 UPDATE_CALENDAR_DELAY,
6374 (Action)sipe_core_update_calendar,
6375 NULL,
6376 SIP_TO_CORE_PRIVATE,
6377 NULL);
6381 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
6383 char *activity = NULL;
6384 const char *epid;
6385 const char *status_id = NULL;
6386 const char *name;
6387 char *uri;
6388 char *self_uri = sip_uri_self(sip);
6389 int avl;
6390 int act;
6391 const char *device_name = NULL;
6392 const char *cal_start_time = NULL;
6393 const char *cal_granularity = NULL;
6394 char *cal_free_busy_base64 = NULL;
6395 struct sipe_buddy *sbuddy;
6396 const sipe_xml *node;
6397 sipe_xml *xn_presentity;
6398 const sipe_xml *xn_availability;
6399 const sipe_xml *xn_activity;
6400 const sipe_xml *xn_display_name;
6401 const sipe_xml *xn_email;
6402 const sipe_xml *xn_phone_number;
6403 const sipe_xml *xn_userinfo;
6404 const sipe_xml *xn_note;
6405 const sipe_xml *xn_oof;
6406 const sipe_xml *xn_state;
6407 const sipe_xml *xn_contact;
6408 char *note;
6409 char *free_activity;
6410 int user_avail;
6411 const char *user_avail_nil;
6412 int res_avail;
6413 time_t user_avail_since = 0;
6414 time_t activity_since = 0;
6416 /* fix for Reuters environment on Linux */
6417 if (data && strstr(data, "encoding=\"utf-16\"")) {
6418 char *tmp_data;
6419 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6420 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
6421 g_free(tmp_data);
6422 } else {
6423 xn_presentity = sipe_xml_parse(data, len);
6426 xn_availability = sipe_xml_child(xn_presentity, "availability");
6427 xn_activity = sipe_xml_child(xn_presentity, "activity");
6428 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
6429 xn_email = sipe_xml_child(xn_presentity, "email");
6430 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
6431 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
6432 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
6433 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
6434 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
6435 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
6436 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
6437 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
6438 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
6439 note = xn_note ? sipe_xml_data(xn_note) : NULL;
6441 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6442 user_avail = 0;
6443 user_avail_since = 0;
6446 free_activity = NULL;
6448 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
6449 uri = sip_uri_from_name(name);
6450 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
6451 epid = sipe_xml_attribute(xn_availability, "epid");
6452 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
6454 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6455 res_avail = sipe_get_availability_by_status(status_id, NULL);
6456 if (user_avail > res_avail) {
6457 res_avail = user_avail;
6458 status_id = sipe_get_status_by_availability(user_avail, NULL);
6461 if (xn_display_name) {
6462 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
6463 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
6464 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
6465 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
6466 char *tel_uri = sip_to_tel_uri(phone_number);
6468 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
6469 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
6470 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
6471 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6473 g_free(tel_uri);
6474 g_free(phone_label);
6475 g_free(phone_number);
6476 g_free(email);
6477 g_free(display_name);
6480 if (xn_contact) {
6481 /* tel */
6482 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
6484 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6485 const char *phone_type = sipe_xml_attribute(node, "type");
6486 char* phone = sipe_xml_data(node);
6488 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
6490 g_free(phone);
6494 /* devicePresence */
6495 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
6496 const sipe_xml *xn_device_name;
6497 const sipe_xml *xn_calendar_info;
6498 const sipe_xml *xn_state;
6499 char *state;
6501 /* deviceName */
6502 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
6503 xn_device_name = sipe_xml_child(node, "deviceName");
6504 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
6507 /* calendarInfo */
6508 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
6509 if (xn_calendar_info) {
6510 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
6512 if (cal_start_time) {
6513 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6514 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6516 if (cal_start_time_t_tmp > cal_start_time_t) {
6517 cal_start_time = cal_start_time_tmp;
6518 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6519 g_free(cal_free_busy_base64);
6520 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6522 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);
6524 } else {
6525 cal_start_time = cal_start_time_tmp;
6526 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6527 g_free(cal_free_busy_base64);
6528 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6530 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
6534 /* state */
6535 xn_state = sipe_xml_child(node, "states/state");
6536 if (xn_state) {
6537 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
6538 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
6540 state = sipe_xml_data(xn_state);
6541 if (dev_avail_since > user_avail_since &&
6542 dev_avail >= res_avail)
6544 res_avail = dev_avail;
6545 if (!is_empty(state))
6547 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6548 g_free(activity);
6549 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6550 } else if (sipe_strequal(state, "presenting")) {
6551 g_free(activity);
6552 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6553 } else {
6554 activity = state;
6555 state = NULL;
6557 activity_since = dev_avail_since;
6559 status_id = sipe_get_status_by_availability(res_avail, &activity);
6561 g_free(state);
6565 /* oof */
6566 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6567 g_free(activity);
6568 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6569 activity_since = 0;
6572 sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri);
6573 if (sbuddy)
6575 g_free(sbuddy->activity);
6576 sbuddy->activity = activity;
6577 activity = NULL;
6579 sbuddy->activity_since = activity_since;
6581 sbuddy->user_avail = user_avail;
6582 sbuddy->user_avail_since = user_avail_since;
6584 g_free(sbuddy->note);
6585 sbuddy->note = NULL;
6586 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6588 sbuddy->is_oof_note = (xn_oof != NULL);
6590 g_free(sbuddy->device_name);
6591 sbuddy->device_name = NULL;
6592 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6594 if (!is_empty(cal_free_busy_base64)) {
6595 g_free(sbuddy->cal_start_time);
6596 sbuddy->cal_start_time = g_strdup(cal_start_time);
6598 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6600 g_free(sbuddy->cal_free_busy_base64);
6601 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6602 cal_free_busy_base64 = NULL;
6604 g_free(sbuddy->cal_free_busy);
6605 sbuddy->cal_free_busy = NULL;
6608 sbuddy->last_non_cal_status_id = status_id;
6609 g_free(sbuddy->last_non_cal_activity);
6610 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6612 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6613 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6615 sip->is_oof_note = sbuddy->is_oof_note;
6617 g_free(sip->note);
6618 sip->note = g_strdup(sbuddy->note);
6620 sip->note_since = time(NULL);
6623 g_free(sip->status);
6624 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6627 g_free(cal_free_busy_base64);
6628 g_free(activity);
6630 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6631 sipe_got_user_status(sip, uri, status_id);
6633 if (!sip->ocs2007 && sipe_strcase_equal(self_uri, uri)) {
6634 sipe_user_info_has_updated(sip, xn_userinfo);
6637 g_free(note);
6638 sipe_xml_free(xn_presentity);
6639 g_free(uri);
6640 g_free(self_uri);
6643 static void sipe_presence_mime_cb(gpointer user_data,
6644 const gchar *type,
6645 const gchar *body,
6646 gsize length)
6648 if (strstr(type,"application/rlmi+xml")) {
6649 process_incoming_notify_rlmi_resub(user_data, body, length);
6650 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6651 process_incoming_notify_msrtc(user_data, body, length);
6652 } else {
6653 process_incoming_notify_rlmi(user_data, body, length);
6657 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
6659 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6661 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6663 if (ctype &&
6664 (strstr(ctype, "application/rlmi+xml") ||
6665 strstr(ctype, "application/msrtc-event-categories+xml")))
6667 if (strstr(ctype, "multipart"))
6669 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sip);
6671 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6673 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
6675 else if(strstr(ctype, "application/rlmi+xml"))
6677 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
6680 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6682 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
6684 else
6686 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
6690 static void sipe_presence_timeout_mime_cb(gpointer user_data,
6691 SIPE_UNUSED_PARAMETER const gchar *type,
6692 const gchar *body,
6693 gsize length)
6695 GSList **buddies = user_data;
6696 sipe_xml *xml = sipe_xml_parse(body, length);
6698 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
6699 const gchar *uri = sipe_xml_attribute(xml, "uri");
6700 const sipe_xml *xn_category;
6703 * automaton: presence is never expected to change
6705 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
6707 for (xn_category = sipe_xml_child(xml, "category");
6708 xn_category;
6709 xn_category = sipe_xml_twin(xn_category)) {
6710 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
6711 "contactCard")) {
6712 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
6713 if (node) {
6714 char *boolean = sipe_xml_data(node);
6715 if (sipe_strequal(boolean, "true")) {
6716 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
6717 uri);
6718 uri = NULL;
6720 g_free(boolean);
6722 break;
6726 if (uri) {
6727 *buddies = g_slist_append(*buddies, sip_uri(uri));
6731 sipe_xml_free(xml);
6734 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
6736 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6737 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6739 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
6741 if (ctype &&
6742 strstr(ctype, "multipart") &&
6743 (strstr(ctype, "application/rlmi+xml") ||
6744 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6745 GSList *buddies = NULL;
6747 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
6749 if (buddies) {
6750 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6751 payload->host = g_strdup(who);
6752 payload->buddies = buddies;
6753 sipe_schedule_action(action_name,
6754 timeout,
6755 sipe_subscribe_presence_batched_routed,
6756 sipe_subscribe_presence_batched_routed_free,
6757 SIP_TO_CORE_PRIVATE,
6758 payload);
6759 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
6762 } else {
6763 sipe_schedule_action(action_name,
6764 timeout,
6765 sipe_subscribe_presence_single,
6766 g_free,
6767 SIP_TO_CORE_PRIVATE,
6768 g_strdup(who));
6769 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
6771 g_free(action_name);
6775 * Dispatcher for all incoming subscription information
6776 * whether it comes from NOTIFY, BENOTIFY requests or
6777 * piggy-backed to subscription's OK responce.
6779 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6780 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6782 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
6784 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6785 const gchar *event = sipmsg_find_header(msg, "Event");
6786 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6787 char *tmp;
6789 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
6790 event ? event : "",
6791 tmp = fix_newlines(msg->body));
6792 g_free(tmp);
6793 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
6795 /* implicit subscriptions */
6796 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6797 sipe_process_imdn(sip, msg);
6800 if (event) {
6801 /* for one off subscriptions (send with Expire: 0) */
6802 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
6804 sipe_process_provisioning_v2(sip, msg);
6806 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
6808 sipe_process_provisioning(sip, msg);
6810 else if (sipe_strcase_equal(event, "presence"))
6812 sipe_process_presence(sip, msg);
6814 else if (sipe_strcase_equal(event, "registration-notify"))
6816 sipe_process_registration_notify(sip, msg);
6819 if (!subscription_state || strstr(subscription_state, "active"))
6821 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
6823 sipe_process_roaming_contacts(sip, msg);
6825 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
6827 sipe_process_roaming_self(sip, msg);
6829 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
6831 sipe_process_roaming_acl(sip, msg);
6833 else if (sipe_strcase_equal(event, "presence.wpending"))
6835 sipe_process_presence_wpending(sip, msg);
6837 else if (sipe_strcase_equal(event, "conference"))
6839 sipe_process_conference(sip, msg);
6844 /* The server sends status 'terminated' */
6845 if (subscription_state && strstr(subscription_state, "terminated") ) {
6846 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6847 gchar *key = sipe_get_subscription_key(event, who);
6849 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
6850 g_free(who);
6852 if (g_hash_table_lookup(sip->subscriptions, key)) {
6853 g_hash_table_remove(sip->subscriptions, key);
6854 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
6857 g_free(key);
6860 if (!request && event) {
6861 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6862 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6863 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
6865 if (timeout) {
6866 /* 2 min ahead of expiration */
6867 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6869 if (sipe_strcase_equal(event, "presence.wpending") &&
6870 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6872 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6873 sipe_schedule_action(action_name,
6874 timeout,
6875 sipe_subscribe_presence_wpending,
6876 NULL,
6877 SIP_TO_CORE_PRIVATE,
6878 NULL);
6879 g_free(action_name);
6881 else if (sipe_strcase_equal(event, "presence") &&
6882 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6884 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6885 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6887 if (sip->batched_support) {
6888 sipe_process_presence_timeout(sip, msg, who, timeout);
6890 else {
6891 sipe_schedule_action(action_name,
6892 timeout,
6893 sipe_subscribe_presence_single,
6894 g_free,
6895 SIP_TO_CORE_PRIVATE,
6896 g_strdup(who));
6897 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
6899 g_free(action_name);
6900 g_free(who);
6905 /* The client responses on received a NOTIFY message */
6906 if (request && !benotify)
6908 send_sip_response(sip->gc, msg, 200, "OK", NULL);
6913 * Whether user manually changed status or
6914 * it was changed automatically due to user
6915 * became inactive/active again
6917 static gboolean
6918 sipe_is_user_state(struct sipe_account_data *sip)
6920 gboolean res;
6921 time_t now = time(NULL);
6923 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6924 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
6926 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6928 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
6929 return res;
6932 static void
6933 send_presence_soap0(struct sipe_account_data *sip,
6934 gboolean do_publish_calendar,
6935 gboolean do_reset_status)
6937 struct sipe_calendar* cal = sip->cal;
6938 int availability = 0;
6939 int activity = 0;
6940 gchar *body;
6941 gchar *tmp;
6942 gchar *tmp2 = NULL;
6943 gchar *res_note = NULL;
6944 gchar *res_oof = NULL;
6945 const gchar *note_pub = NULL;
6946 gchar *states = NULL;
6947 gchar *calendar_data = NULL;
6948 gchar *epid = get_epid(sip);
6949 time_t now = time(NULL);
6950 gchar *since_time_str = sipe_utils_time_to_str(now);
6951 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
6952 const char *user_input;
6953 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
6955 if (oof_note && sip->note) {
6956 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
6957 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6960 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
6962 if (!sip->initial_state_published ||
6963 do_reset_status)
6965 g_free(sip->status);
6966 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6969 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6971 /* Note */
6972 if (pub_oof) {
6973 note_pub = oof_note;
6974 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6975 cal->published = TRUE;
6976 } else if (sip->note) {
6977 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
6978 g_free(sip->note);
6979 sip->note = NULL;
6980 sip->is_oof_note = FALSE;
6981 sip->note_since = 0;
6982 } else {
6983 note_pub = sip->note;
6984 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6988 if (note_pub)
6990 /* to protocol internal plain text format */
6991 tmp = sipe_backend_markup_strip_html(note_pub);
6992 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6993 g_free(tmp);
6996 /* User State */
6997 if (!do_reset_status) {
6998 if (sipe_is_user_state(sip) && !do_publish_calendar && sip->initial_state_published)
7000 gchar *activity_token = NULL;
7001 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
7003 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
7004 avail_2007,
7005 since_time_str,
7006 epid,
7007 activity_token);
7008 g_free(activity_token);
7010 else /* preserve existing publication */
7012 if (sip->user_states) {
7013 states = g_strdup(sip->user_states);
7016 } else {
7017 /* do nothing - then User state will be erased */
7019 sip->initial_state_published = TRUE;
7021 /* CalendarInfo */
7022 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
7024 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
7025 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
7026 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
7027 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
7028 fb_start_str,
7029 free_busy_base64);
7030 g_free(fb_start_str);
7031 g_free(free_busy_base64);
7034 user_input = !sipe_is_user_state(sip) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
7036 /* forming resulting XML */
7037 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
7038 sip->username,
7039 availability,
7040 activity,
7041 (tmp = g_ascii_strup(g_get_host_name(), -1)),
7042 res_note ? res_note : "",
7043 res_oof ? res_oof : "",
7044 states ? states : "",
7045 calendar_data ? calendar_data : "",
7046 epid,
7047 since_time_str,
7048 since_time_str,
7049 user_input);
7050 g_free(tmp);
7051 g_free(tmp2);
7052 g_free(res_note);
7053 g_free(states);
7054 g_free(calendar_data);
7056 send_soap_request(sip, body);
7058 g_free(body);
7059 g_free(since_time_str);
7060 g_free(epid);
7063 void
7064 send_presence_soap(struct sipe_account_data *sip,
7065 gboolean do_publish_calendar)
7067 return send_presence_soap0(sip, do_publish_calendar, FALSE);
7071 static gboolean
7072 process_send_presence_category_publish_response(struct sipe_account_data *sip,
7073 struct sipmsg *msg,
7074 struct transaction *trans)
7076 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
7078 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
7079 sipe_xml *xml;
7080 const sipe_xml *node;
7081 gchar *fault_code;
7082 GHashTable *faults;
7083 int index_our;
7084 gboolean has_device_publication = FALSE;
7086 xml = sipe_xml_parse(msg->body, msg->bodylen);
7088 /* test if version mismatch fault */
7089 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
7090 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
7091 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
7092 g_free(fault_code);
7093 sipe_xml_free(xml);
7094 return TRUE;
7096 g_free(fault_code);
7098 /* accumulating information about faulty versions */
7099 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
7100 for (node = sipe_xml_child(xml, "details/operation");
7101 node;
7102 node = sipe_xml_twin(node))
7104 const gchar *index = sipe_xml_attribute(node, "index");
7105 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
7107 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
7108 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
7110 sipe_xml_free(xml);
7112 /* here we are parsing own request to figure out what publication
7113 * referensed here only by index went wrong
7115 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
7117 /* publication */
7118 for (node = sipe_xml_child(xml, "publications/publication"),
7119 index_our = 1; /* starts with 1 - our first publication */
7120 node;
7121 node = sipe_xml_twin(node), index_our++)
7123 gchar *idx = g_strdup_printf("%d", index_our);
7124 const gchar *curVersion = g_hash_table_lookup(faults, idx);
7125 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
7126 g_free(idx);
7128 if (sipe_strequal("device", categoryName)) {
7129 has_device_publication = TRUE;
7132 if (curVersion) { /* fault exist on this index */
7133 const gchar *container = sipe_xml_attribute(node, "container");
7134 const gchar *instance = sipe_xml_attribute(node, "instance");
7135 /* key is <category><instance><container> */
7136 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
7137 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
7139 if (category) {
7140 struct sipe_publication *publication =
7141 g_hash_table_lookup(category, key);
7143 SIPE_DEBUG_INFO("key is %s", key);
7145 if (publication) {
7146 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7147 key, curVersion, publication->version);
7148 /* updating publication's version to the correct one */
7149 publication->version = atoi(curVersion);
7151 } else {
7152 /* We somehow lost this category from our publications... */
7153 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
7154 publication->category = g_strdup(categoryName);
7155 publication->instance = atoi(instance);
7156 publication->container = atoi(container);
7157 publication->version = atoi(curVersion);
7158 category = g_hash_table_new_full(g_str_hash, g_str_equal,
7159 g_free, (GDestroyNotify)free_publication);
7160 g_hash_table_insert(category, g_strdup(key), publication);
7161 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
7162 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
7164 g_free(key);
7167 sipe_xml_free(xml);
7168 g_hash_table_destroy(faults);
7170 /* rebublishing with right versions */
7171 if (has_device_publication) {
7172 send_publish_category_initial(sip);
7173 } else {
7174 send_presence_status(SIP_TO_CORE_PRIVATE, NULL);
7177 return TRUE;
7181 * Returns 'device' XML part for publication.
7182 * Must be g_free'd after use.
7184 static gchar *
7185 sipe_publish_get_category_device(struct sipe_account_data *sip)
7187 gchar *uri;
7188 gchar *doc;
7189 gchar *epid = get_epid(sip);
7190 gchar *uuid = generateUUIDfromEPID(epid);
7191 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
7192 /* key is <category><instance><container> */
7193 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
7194 struct sipe_publication *publication =
7195 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
7197 g_free(key);
7198 g_free(epid);
7200 uri = sip_uri_self(sip);
7201 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
7202 device_instance,
7203 publication ? publication->version : 0,
7204 uuid,
7205 uri,
7206 "00:00:00+01:00", /* @TODO make timezone real*/
7207 g_get_host_name()
7210 g_free(uri);
7211 g_free(uuid);
7213 return doc;
7217 * A service method - use
7218 * - send_publish_get_category_state_machine and
7219 * - send_publish_get_category_state_user instead.
7220 * Must be g_free'd after use.
7222 static gchar *
7223 sipe_publish_get_category_state(struct sipe_account_data *sip,
7224 gboolean is_user_state)
7226 int availability = sipe_get_availability_by_status(sip->status, NULL);
7227 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
7228 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
7229 /* key is <category><instance><container> */
7230 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7231 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7232 struct sipe_publication *publication_2 =
7233 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7234 struct sipe_publication *publication_3 =
7235 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7237 g_free(key_2);
7238 g_free(key_3);
7240 if (publication_2 && (publication_2->availability == availability))
7242 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7243 return NULL; /* nothing to update */
7246 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
7247 instance,
7248 publication_2 ? publication_2->version : 0,
7249 availability,
7250 instance,
7251 publication_3 ? publication_3->version : 0,
7252 availability);
7256 * Only Busy and OOF calendar event are published.
7257 * Different instances are used for that.
7259 * Must be g_free'd after use.
7261 static gchar *
7262 sipe_publish_get_category_state_calendar(struct sipe_account_data *sip,
7263 struct sipe_cal_event *event,
7264 const char *uri,
7265 int cal_satus)
7267 gchar *start_time_str;
7268 int availability = 0;
7269 gchar *res;
7270 gchar *tmp = NULL;
7271 guint instance = (cal_satus == SIPE_CAL_OOF) ?
7272 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR_OOF) :
7273 sipe_get_pub_instance(sip, SIPE_PUB_STATE_CALENDAR);
7275 /* key is <category><instance><container> */
7276 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
7277 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
7278 struct sipe_publication *publication_2 =
7279 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
7280 struct sipe_publication *publication_3 =
7281 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
7283 g_free(key_2);
7284 g_free(key_3);
7286 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
7287 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7288 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
7289 return NULL;
7292 if (event &&
7293 publication_3 &&
7294 (publication_3->availability == availability) &&
7295 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
7297 g_free(tmp);
7298 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7299 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
7300 return NULL; /* nothing to update */
7302 g_free(tmp);
7304 if (event &&
7305 (event->cal_status == SIPE_CAL_BUSY ||
7306 event->cal_status == SIPE_CAL_OOF))
7308 gchar *availability_xml_str = NULL;
7309 gchar *activity_xml_str = NULL;
7311 if (event->cal_status == SIPE_CAL_BUSY) {
7312 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
7315 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
7316 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7317 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
7318 "minAvailability=\"6500\"",
7319 "maxAvailability=\"8999\"");
7320 } else if (event->cal_status == SIPE_CAL_OOF) {
7321 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
7322 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
7323 "minAvailability=\"12000\"",
7324 "");
7326 start_time_str = sipe_utils_time_to_str(event->start_time);
7328 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
7329 instance,
7330 publication_2 ? publication_2->version : 0,
7331 uri,
7332 start_time_str,
7333 availability_xml_str ? availability_xml_str : "",
7334 activity_xml_str ? activity_xml_str : "",
7335 event->subject ? event->subject : "",
7336 event->location ? event->location : "",
7338 instance,
7339 publication_3 ? publication_3->version : 0,
7340 uri,
7341 start_time_str,
7342 availability_xml_str ? availability_xml_str : "",
7343 activity_xml_str ? activity_xml_str : "",
7344 event->subject ? event->subject : "",
7345 event->location ? event->location : ""
7347 g_free(start_time_str);
7348 g_free(availability_xml_str);
7349 g_free(activity_xml_str);
7352 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7354 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
7355 instance,
7356 publication_2 ? publication_2->version : 0,
7358 instance,
7359 publication_3 ? publication_3->version : 0
7363 return res;
7367 * Returns 'machineState' XML part for publication.
7368 * Must be g_free'd after use.
7370 static gchar *
7371 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
7373 return sipe_publish_get_category_state(sip, FALSE);
7377 * Returns 'userState' XML part for publication.
7378 * Must be g_free'd after use.
7380 static gchar *
7381 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
7383 return sipe_publish_get_category_state(sip, TRUE);
7387 * Returns 'note' XML part for publication.
7388 * Must be g_free'd after use.
7390 * Protocol format for Note is plain text.
7392 * @param note a note in Sipe internal HTML format
7393 * @param note_type either personal or OOF
7395 static gchar *
7396 sipe_publish_get_category_note(struct sipe_account_data *sip,
7397 const char *note, /* html */
7398 const char *note_type,
7399 time_t note_start,
7400 time_t note_end)
7402 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sip, SIPE_PUB_NOTE_OOF) : 0;
7403 /* key is <category><instance><container> */
7404 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7405 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7406 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7408 struct sipe_publication *publication_note_200 =
7409 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7410 struct sipe_publication *publication_note_300 =
7411 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7412 struct sipe_publication *publication_note_400 =
7413 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7415 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7416 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7417 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7418 char *res, *tmp1, *tmp2, *tmp3;
7419 char *start_time_attr;
7420 char *end_time_attr;
7422 g_free(tmp);
7423 tmp = NULL;
7424 g_free(key_note_200);
7425 g_free(key_note_300);
7426 g_free(key_note_400);
7428 /* we even need to republish empty note */
7429 if (sipe_strequal(n1, n2))
7431 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7432 g_free(n1);
7433 return NULL; /* nothing to update */
7436 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7437 g_free(tmp);
7438 tmp = NULL;
7439 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7440 g_free(tmp);
7442 if (n1) {
7443 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7444 instance,
7445 200,
7446 publication_note_200 ? publication_note_200->version : 0,
7447 note_type,
7448 start_time_attr ? start_time_attr : "",
7449 end_time_attr ? end_time_attr : "",
7450 n1);
7452 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7453 instance,
7454 300,
7455 publication_note_300 ? publication_note_300->version : 0,
7456 note_type,
7457 start_time_attr ? start_time_attr : "",
7458 end_time_attr ? end_time_attr : "",
7459 n1);
7461 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7462 instance,
7463 400,
7464 publication_note_400 ? publication_note_400->version : 0,
7465 note_type,
7466 start_time_attr ? start_time_attr : "",
7467 end_time_attr ? end_time_attr : "",
7468 n1);
7469 } else {
7470 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7471 "note",
7472 instance,
7473 200,
7474 publication_note_200 ? publication_note_200->version : 0,
7475 "static");
7476 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7477 "note",
7478 instance,
7479 300,
7480 publication_note_200 ? publication_note_200->version : 0,
7481 "static");
7482 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7483 "note",
7484 instance,
7485 400,
7486 publication_note_200 ? publication_note_200->version : 0,
7487 "static");
7489 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7491 g_free(start_time_attr);
7492 g_free(end_time_attr);
7493 g_free(tmp1);
7494 g_free(tmp2);
7495 g_free(tmp3);
7496 g_free(n1);
7498 return res;
7502 * Returns 'calendarData' XML part with WorkingHours for publication.
7503 * Must be g_free'd after use.
7505 static gchar *
7506 sipe_publish_get_category_cal_working_hours(struct sipe_account_data *sip)
7508 struct sipe_calendar* cal = sip->cal;
7510 /* key is <category><instance><container> */
7511 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7512 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7513 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7514 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7515 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7516 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7518 struct sipe_publication *publication_cal_1 =
7519 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7520 struct sipe_publication *publication_cal_100 =
7521 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7522 struct sipe_publication *publication_cal_200 =
7523 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7524 struct sipe_publication *publication_cal_300 =
7525 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7526 struct sipe_publication *publication_cal_400 =
7527 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7528 struct sipe_publication *publication_cal_32000 =
7529 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7531 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
7532 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7534 g_free(key_cal_1);
7535 g_free(key_cal_100);
7536 g_free(key_cal_200);
7537 g_free(key_cal_300);
7538 g_free(key_cal_400);
7539 g_free(key_cal_32000);
7541 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
7542 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7543 return NULL;
7546 if (sipe_strequal(n1, n2))
7548 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7549 return NULL; /* nothing to update */
7552 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7553 /* 1 */
7554 publication_cal_1 ? publication_cal_1->version : 0,
7555 cal->email,
7556 cal->working_hours_xml_str,
7557 /* 100 - Public */
7558 publication_cal_100 ? publication_cal_100->version : 0,
7559 /* 200 - Company */
7560 publication_cal_200 ? publication_cal_200->version : 0,
7561 cal->email,
7562 cal->working_hours_xml_str,
7563 /* 300 - Team */
7564 publication_cal_300 ? publication_cal_300->version : 0,
7565 cal->email,
7566 cal->working_hours_xml_str,
7567 /* 400 - Personal */
7568 publication_cal_400 ? publication_cal_400->version : 0,
7569 cal->email,
7570 cal->working_hours_xml_str,
7571 /* 32000 - Blocked */
7572 publication_cal_32000 ? publication_cal_32000->version : 0
7577 * Returns 'calendarData' XML part with FreeBusy for publication.
7578 * Must be g_free'd after use.
7580 static gchar *
7581 sipe_publish_get_category_cal_free_busy(struct sipe_account_data *sip)
7583 struct sipe_calendar* cal = sip->cal;
7584 guint cal_data_instance = sipe_get_pub_instance(sip, SIPE_PUB_CALENDAR_DATA);
7585 char *fb_start_str;
7586 char *free_busy_base64;
7587 const char *st;
7588 const char *fb;
7589 char *res;
7591 /* key is <category><instance><container> */
7592 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7593 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7594 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7595 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7596 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7597 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7599 struct sipe_publication *publication_cal_1 =
7600 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7601 struct sipe_publication *publication_cal_100 =
7602 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7603 struct sipe_publication *publication_cal_200 =
7604 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7605 struct sipe_publication *publication_cal_300 =
7606 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7607 struct sipe_publication *publication_cal_400 =
7608 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7609 struct sipe_publication *publication_cal_32000 =
7610 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7612 g_free(key_cal_1);
7613 g_free(key_cal_100);
7614 g_free(key_cal_200);
7615 g_free(key_cal_300);
7616 g_free(key_cal_400);
7617 g_free(key_cal_32000);
7619 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
7620 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7621 return NULL;
7624 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
7625 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
7627 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7628 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7630 /* we will rebuplish the same data to refresh publication time,
7631 * so if data from multiple sources, most recent will be choosen
7633 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7635 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7636 // g_free(fb_start_str);
7637 // g_free(free_busy_base64);
7638 // return NULL; /* nothing to update */
7641 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7642 /* 1 */
7643 cal_data_instance,
7644 publication_cal_1 ? publication_cal_1->version : 0,
7645 /* 100 - Public */
7646 cal_data_instance,
7647 publication_cal_100 ? publication_cal_100->version : 0,
7648 /* 200 - Company */
7649 cal_data_instance,
7650 publication_cal_200 ? publication_cal_200->version : 0,
7651 cal->email,
7652 fb_start_str,
7653 free_busy_base64,
7654 /* 300 - Team */
7655 cal_data_instance,
7656 publication_cal_300 ? publication_cal_300->version : 0,
7657 cal->email,
7658 fb_start_str,
7659 free_busy_base64,
7660 /* 400 - Personal */
7661 cal_data_instance,
7662 publication_cal_400 ? publication_cal_400->version : 0,
7663 cal->email,
7664 fb_start_str,
7665 free_busy_base64,
7666 /* 32000 - Blocked */
7667 cal_data_instance,
7668 publication_cal_32000 ? publication_cal_32000->version : 0
7671 g_free(fb_start_str);
7672 g_free(free_busy_base64);
7673 return res;
7676 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
7678 gchar *uri;
7679 gchar *doc;
7680 gchar *tmp;
7681 gchar *hdr;
7683 uri = sip_uri_self(sip);
7684 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7685 uri,
7686 publications);
7688 tmp = get_contact(sip);
7689 hdr = g_strdup_printf("Contact: %s\r\n"
7690 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7692 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7694 g_free(tmp);
7695 g_free(hdr);
7696 g_free(uri);
7697 g_free(doc);
7700 static void
7701 send_publish_category_initial(struct sipe_account_data *sip)
7703 gchar *pub_device = sipe_publish_get_category_device(sip);
7704 gchar *pub_machine;
7705 gchar *publications;
7707 g_free(sip->status);
7708 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7710 pub_machine = sipe_publish_get_category_state_machine(sip);
7711 publications = g_strdup_printf("%s%s",
7712 pub_device,
7713 pub_machine ? pub_machine : "");
7714 g_free(pub_device);
7715 g_free(pub_machine);
7717 send_presence_publish(sip, publications);
7718 g_free(publications);
7721 static void
7722 send_presence_category_publish(struct sipe_account_data *sip)
7724 gchar *pub_state = sipe_is_user_state(sip) ?
7725 sipe_publish_get_category_state_user(sip) :
7726 sipe_publish_get_category_state_machine(sip);
7727 gchar *pub_note = sipe_publish_get_category_note(sip,
7728 sip->note,
7729 sip->is_oof_note ? "OOF" : "personal",
7732 gchar *publications;
7734 if (!pub_state && !pub_note) {
7735 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
7736 return;
7739 publications = g_strdup_printf("%s%s",
7740 pub_state ? pub_state : "",
7741 pub_note ? pub_note : "");
7743 g_free(pub_state);
7744 g_free(pub_note);
7746 send_presence_publish(sip, publications);
7747 g_free(publications);
7751 * Publishes self status
7752 * based on own calendar information.
7754 * For 2007+
7756 void
7757 publish_calendar_status_self(struct sipe_core_private *sipe_private,
7758 SIPE_UNUSED_PARAMETER void *unused)
7760 struct sipe_account_data *sip = sipe_private->temporary;
7761 struct sipe_cal_event* event = NULL;
7762 gchar *pub_cal_working_hours = NULL;
7763 gchar *pub_cal_free_busy = NULL;
7764 gchar *pub_calendar = NULL;
7765 gchar *pub_calendar2 = NULL;
7766 gchar *pub_oof_note = NULL;
7767 const gchar *oof_note;
7768 time_t oof_start = 0;
7769 time_t oof_end = 0;
7771 if (!sip->cal) {
7772 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
7773 return;
7776 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
7777 if (sip->cal->cal_events) {
7778 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
7781 if (!event) {
7782 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
7783 } else {
7784 char *desc = sipe_cal_event_describe(event);
7785 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7786 g_free(desc);
7789 /* Logic
7790 if OOF
7791 OOF publish, Busy clean
7792 ilse if Busy
7793 OOF clean, Busy publish
7794 else
7795 OOF clean, Busy clean
7797 if (event && event->cal_status == SIPE_CAL_OOF) {
7798 pub_calendar = sipe_publish_get_category_state_calendar(sip, event, sip->cal->email, SIPE_CAL_OOF);
7799 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->cal->email, SIPE_CAL_BUSY);
7800 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7801 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->cal->email, SIPE_CAL_OOF);
7802 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, event, sip->cal->email, SIPE_CAL_BUSY);
7803 } else {
7804 pub_calendar = sipe_publish_get_category_state_calendar(sip, NULL, sip->cal->email, SIPE_CAL_OOF);
7805 pub_calendar2 = sipe_publish_get_category_state_calendar(sip, NULL, sip->cal->email, SIPE_CAL_BUSY);
7808 oof_note = sipe_ews_get_oof_note(sip->cal);
7809 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
7810 oof_start = sip->cal->oof_start;
7811 oof_end = sip->cal->oof_end;
7813 pub_oof_note = sipe_publish_get_category_note(sip, oof_note, "OOF", oof_start, oof_end);
7815 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sip);
7816 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sip);
7818 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7819 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
7820 } else {
7821 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7822 pub_cal_working_hours ? pub_cal_working_hours : "",
7823 pub_cal_free_busy ? pub_cal_free_busy : "",
7824 pub_calendar ? pub_calendar : "",
7825 pub_calendar2 ? pub_calendar2 : "",
7826 pub_oof_note ? pub_oof_note : "");
7828 send_presence_publish(sip, publications);
7829 g_free(publications);
7832 g_free(pub_cal_working_hours);
7833 g_free(pub_cal_free_busy);
7834 g_free(pub_calendar);
7835 g_free(pub_calendar2);
7836 g_free(pub_oof_note);
7838 /* repeat scheduling */
7839 sipe_sched_calendar_status_self_publish(sip, time(NULL));
7842 static void send_presence_status(struct sipe_core_private *sipe_private,
7843 SIPE_UNUSED_PARAMETER void *unused)
7845 struct sipe_account_data *sip = sipe_private->temporary;
7846 PurpleStatus * status = purple_account_get_active_status(sip->account);
7848 if (!status) return;
7850 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
7851 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7852 sipe_is_user_state(sip) ? "USER" : "MACHINE");
7854 if (sip->ocs2007) {
7855 send_presence_category_publish(sip);
7856 } else {
7857 send_presence_soap(sip, FALSE);
7861 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
7863 gboolean found = FALSE;
7864 const char *method = msg->method ? msg->method : "NOT FOUND";
7865 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
7866 if (msg->response == 0) { /* request */
7867 if (sipe_strequal(method, "MESSAGE")) {
7868 process_incoming_message(sip, msg);
7869 found = TRUE;
7870 } else if (sipe_strequal(method, "NOTIFY")) {
7871 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
7872 process_incoming_notify(sip, msg, TRUE, FALSE);
7873 found = TRUE;
7874 } else if (sipe_strequal(method, "BENOTIFY")) {
7875 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
7876 process_incoming_notify(sip, msg, TRUE, TRUE);
7877 found = TRUE;
7878 } else if (sipe_strequal(method, "INVITE")) {
7879 process_incoming_invite(sip, msg);
7880 found = TRUE;
7881 } else if (sipe_strequal(method, "REFER")) {
7882 process_incoming_refer(sip, msg);
7883 found = TRUE;
7884 } else if (sipe_strequal(method, "OPTIONS")) {
7885 process_incoming_options(sip, msg);
7886 found = TRUE;
7887 } else if (sipe_strequal(method, "INFO")) {
7888 process_incoming_info(sip, msg);
7889 found = TRUE;
7890 } else if (sipe_strequal(method, "ACK")) {
7891 // ACK's don't need any response
7892 found = TRUE;
7893 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7894 // LCS 2005 sends us these - just respond 200 OK
7895 found = TRUE;
7896 send_sip_response(sip->gc, msg, 200, "OK", NULL);
7897 } else if (sipe_strequal(method, "BYE")) {
7898 process_incoming_bye(sip, msg);
7899 found = TRUE;
7900 } else {
7901 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
7903 } else { /* response */
7904 struct transaction *trans = transactions_find(sip, msg);
7905 if (trans) {
7906 if (msg->response == 407) {
7907 gchar *resend, *auth;
7908 const gchar *ptmp;
7910 if (sip->proxy.retries > 30) return;
7911 sip->proxy.retries++;
7912 /* do proxy authentication */
7914 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7916 fill_auth(ptmp, &sip->proxy);
7917 auth = auth_header(sip, &sip->proxy, trans->msg);
7918 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7919 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7920 g_free(auth);
7921 resend = sipmsg_to_string(trans->msg);
7922 /* resend request */
7923 sipe_backend_transport_sip_message(sip->public->transport, resend);
7924 g_free(resend);
7925 } else {
7926 if (msg->response < 200) {
7927 /* ignore provisional response */
7928 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
7929 } else {
7930 sip->proxy.retries = 0;
7931 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7932 if (msg->response == 401)
7934 sip->registrar.retries++;
7936 else
7938 sip->registrar.retries = 0;
7940 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
7941 } else {
7942 if (msg->response == 401) {
7943 gchar *resend, *auth, *ptmp;
7944 const char* auth_scheme;
7946 if (sip->registrar.retries > 4) return;
7947 sip->registrar.retries++;
7949 auth_scheme = sipe_get_auth_scheme_name(sip);
7950 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7952 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
7953 if (!ptmp) {
7954 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7955 purple_connection_error_reason(sip->gc,
7956 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
7957 tmp2);
7958 g_free(tmp2);
7959 return;
7962 fill_auth(ptmp, &sip->registrar);
7963 auth = auth_header(sip, &sip->registrar, trans->msg);
7964 sipmsg_remove_header_now(trans->msg, "Authorization");
7965 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7966 g_free(auth);
7967 resend = sipmsg_to_string(trans->msg);
7968 /* resend request */
7969 sipe_backend_transport_sip_message(sip->public->transport, resend);
7970 g_free(resend);
7974 if (trans->callback) {
7975 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
7976 /* call the callback to process response*/
7977 (trans->callback)(sip, msg, trans);
7980 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
7981 transactions_remove(sip, trans);
7985 found = TRUE;
7986 } else {
7987 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
7990 if (!found) {
7991 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
7995 void sipe_core_transport_sip_message(struct sipe_transport_connection *conn)
7997 struct sipe_core_private *sipe_private = conn->user_data;
7998 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7999 gchar *cur = conn->buffer;
8001 /* according to the RFC remove CRLF at the beginning */
8002 while (*cur == '\r' || *cur == '\n') {
8003 cur++;
8005 if (cur != conn->buffer)
8006 sipe_utils_shrink_buffer(conn, cur);
8008 /* Received a full Header? */
8009 sip->processing_input = TRUE;
8010 while (sip->processing_input &&
8011 ((cur = strstr(conn->buffer, "\r\n\r\n")) != NULL)) {
8012 struct sipmsg *msg;
8013 gchar *tmp;
8014 guint remainder;
8015 time_t currtime = time(NULL);
8016 cur += 2;
8017 cur[0] = '\0';
8018 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime), tmp = fix_newlines(conn->buffer));
8019 g_free(tmp);
8020 msg = sipmsg_parse_header(conn->buffer);
8021 cur[0] = '\r';
8022 cur += 2;
8023 remainder = conn->buffer_used - (cur - conn->buffer);
8024 if (msg && remainder >= (guint) msg->bodylen) {
8025 char *dummy = g_malloc(msg->bodylen + 1);
8026 memcpy(dummy, cur, msg->bodylen);
8027 dummy[msg->bodylen] = '\0';
8028 msg->body = dummy;
8029 cur += msg->bodylen;
8030 sipe_utils_shrink_buffer(conn, cur);
8031 } else {
8032 if (msg){
8033 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder, msg->bodylen, (int)strlen(conn->buffer));
8034 sipmsg_free(msg);
8036 return;
8039 /*if (msg->body) {
8040 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8043 // Verify the signature before processing it
8044 if (sip->registrar.gssapi_context) {
8045 struct sipmsg_breakdown msgbd;
8046 gchar *signature_input_str;
8047 gchar *rspauth;
8048 msgbd.msg = msg;
8049 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
8050 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
8052 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
8054 if (rspauth != NULL) {
8055 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
8056 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8057 process_input_message(sip, msg);
8058 } else {
8059 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8060 purple_connection_error_reason(sip->gc,
8061 PURPLE_CONNECTION_ERROR_NETWORK_ERROR ,
8062 _("Invalid message signature received"));
8064 } else if (msg->response == 401) {
8065 purple_connection_error_reason(sip->gc,
8066 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
8067 _("Authentication failed"));
8069 g_free(signature_input_str);
8071 g_free(rspauth);
8072 sipmsg_breakdown_free(&msgbd);
8073 } else {
8074 process_input_message(sip, msg);
8077 sipmsg_free(msg);
8081 static guint sipe_ht_hash_nick(const char *nick)
8083 char *lc = g_utf8_strdown(nick, -1);
8084 guint bucket = g_str_hash(lc);
8085 g_free(lc);
8087 return bucket;
8090 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
8092 char *nick1_norm = NULL;
8093 char *nick2_norm = NULL;
8094 gboolean equal;
8096 if (nick1 == NULL && nick2 == NULL) return TRUE;
8097 if (nick1 == NULL || nick2 == NULL ||
8098 !g_utf8_validate(nick1, -1, NULL) ||
8099 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
8101 nick1_norm = g_utf8_casefold(nick1, -1);
8102 nick2_norm = g_utf8_casefold(nick2, -1);
8103 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
8104 g_free(nick2_norm);
8105 g_free(nick1_norm);
8107 return equal;
8110 struct sipe_service_data {
8111 const char *service;
8112 const char *transport;
8113 guint type;
8116 static const struct sipe_service_data *current_service = NULL;
8118 void sipe_core_transport_sip_ssl_connect_failure(struct sipe_transport_connection *conn)
8120 struct sipe_core_private *sipe_private = conn->user_data;
8121 current_service = SIPE_ACCOUNT_DATA_PRIVATE->service_data;
8122 if (current_service) {
8123 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8124 current_service->transport ? current_service->transport : "NULL",
8125 current_service->service ? current_service->service : "NULL");
8129 void sipe_core_transport_sip_connected(struct sipe_transport_connection *conn)
8131 struct sipe_core_private *sipe_private = conn->user_data;
8132 do_register(SIPE_ACCOUNT_DATA_PRIVATE);
8135 /* Service list for autodection */
8136 static const struct sipe_service_data service_autodetect[] = {
8137 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8138 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8139 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8140 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8141 { NULL, NULL, 0 }
8144 /* Service list for SSL/TLS */
8145 static const struct sipe_service_data service_tls[] = {
8146 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
8147 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
8148 { NULL, NULL, 0 }
8151 /* Service list for TCP */
8152 static const struct sipe_service_data service_tcp[] = {
8153 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
8154 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
8155 { NULL, NULL, 0 }
8158 static void srvresolved(PurpleSrvResponse *, int, gpointer);
8159 static void resolve_next_service(struct sipe_account_data *sip,
8160 const struct sipe_service_data *start)
8162 if (start) {
8163 sip->service_data = start;
8164 } else {
8165 sip->service_data++;
8166 if (sip->service_data->service == NULL) {
8167 guint type = SIP_TO_CORE_PRIVATE->transport_type;
8169 /* Try connecting to the SIP hostname directly */
8170 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8171 if (sip->auto_transport) {
8172 type = SIPE_TRANSPORT_TLS;
8175 sipe_server_register(SIP_TO_CORE_PRIVATE, type,
8176 g_strdup(SIP_TO_CORE_PUBLIC->sip_domain),
8178 return;
8182 /* Try to resolve next service */
8183 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
8184 sip->service_data->transport,
8185 SIP_TO_CORE_PUBLIC->sip_domain,
8186 srvresolved, sip);
8189 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
8191 struct sipe_account_data *sip = data;
8193 sip->srv_query_data = NULL;
8195 /* find the host to connect to */
8196 if (results) {
8197 gchar *hostname = g_strdup(resp->hostname);
8198 int port = resp->port;
8199 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8200 hostname, port);
8201 g_free(resp);
8203 sipe_server_register(SIP_TO_CORE_PRIVATE,
8204 sip->service_data->type,
8205 hostname, port);
8206 } else {
8207 resolve_next_service(sip, NULL);
8211 /* temporary function */
8212 void sipe_purple_setup(struct sipe_core_public *sipe_public,
8213 PurpleConnection *gc)
8215 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
8216 sip->gc = gc;
8217 sip->account = purple_connection_get_account(gc);
8220 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
8221 const gchar *login_domain,
8222 const gchar *login_account,
8223 const gchar *password,
8224 const gchar *email,
8225 const gchar *email_url,
8226 const gchar *calendar,
8227 const gchar **errmsg)
8229 struct sipe_core_private *sipe_private;
8230 struct sipe_account_data *sip;
8231 gchar **user_domain;
8233 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
8235 /* ensure that sign-in name doesn't contain invalid characters */
8236 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
8237 *errmsg = _("SIP Exchange user name contains invalid characters");
8238 return NULL;
8241 /* ensure that sign-in name format is name@domain */
8242 if (!strchr(signin_name, '@') ||
8243 g_str_has_prefix(signin_name, "@") ||
8244 g_str_has_suffix(signin_name, "@")) {
8245 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
8246 return NULL;
8249 /* ensure that email format is name@domain (if provided) */
8250 if (!is_empty(email) &&
8251 (!strchr(email, '@') ||
8252 g_str_has_prefix(email, "@") ||
8253 g_str_has_suffix(email, "@")))
8255 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
8256 return NULL;
8259 /* ensure that user name doesn't contain spaces */
8260 user_domain = g_strsplit(signin_name, "@", 2);
8261 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
8262 if (strchr(user_domain[0], ' ') != NULL) {
8263 g_strfreev(user_domain);
8264 *errmsg = _("SIP Exchange user name contains whitespace");
8265 return NULL;
8268 /* ensure that email_url is in proper format if Domino enabled (if provided) */
8269 /* For Domino: https://[domino_server]/[databasename].nsf */
8270 if (sipe_strequal(calendar, "DOMINO") && !is_empty(email_url)) {
8271 char *tmp = g_ascii_strdown(email_url, -1);
8272 if (!g_str_has_prefix(tmp, "https://") ||
8273 !g_str_has_suffix(tmp, ".nsf"))
8275 g_free(tmp);
8276 g_strfreev(user_domain);
8277 *errmsg = _("Lotus Domino URL should be valid if provided\nExample: https://domino.corp.com/maildatabase.nsf");
8278 return NULL;
8280 g_free(tmp);
8283 sipe_private = g_new0(struct sipe_core_private, 1);
8284 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
8285 sip->public = (struct sipe_core_public *)sipe_private;
8286 sip->private = sipe_private;
8287 sip->reregister_set = FALSE;
8288 sip->reauthenticate_set = FALSE;
8289 sip->subscribed = FALSE;
8290 sip->subscribed_buddies = FALSE;
8291 sip->initial_state_published = FALSE;
8292 sip->username = g_strdup(signin_name);
8293 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
8294 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
8295 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
8296 sip->password = g_strdup(password);
8297 sipe_private->public.sip_name = g_strdup(user_domain[0]);
8298 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
8299 g_strfreev(user_domain);
8301 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
8302 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
8303 g_free, (GDestroyNotify)g_hash_table_destroy);
8304 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
8305 g_free, (GDestroyNotify)sipe_subscription_free);
8306 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
8307 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
8309 return((struct sipe_core_public *)sipe_private);
8312 void sipe_core_transport_sip_connect(struct sipe_core_public *sipe_public,
8313 guint transport,
8314 const gchar *server,
8315 const gchar *port)
8317 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
8319 sip->auto_transport = FALSE;
8320 if (server) {
8321 /* Use user specified server[:port] */
8322 int port_number = 0;
8324 if (port)
8325 port_number = atoi(port);
8327 SIPE_DEBUG_INFO("sipe_core_connect: user specified SIP server %s:%d",
8328 server, port_number);
8330 sipe_server_register(SIPE_CORE_PRIVATE, transport,
8331 g_strdup(server), port_number);
8332 } else {
8333 /* Server auto-discovery */
8334 switch (transport) {
8335 case SIPE_TRANSPORT_AUTO:
8336 sip->auto_transport = TRUE;
8337 if (current_service &&
8338 current_service->transport != NULL &&
8339 current_service->service != NULL) {
8340 current_service++;
8341 resolve_next_service(sip, current_service);
8342 } else {
8343 resolve_next_service(sip, service_autodetect);
8345 break;
8346 case SIPE_TRANSPORT_TLS:
8347 resolve_next_service(sip, service_tls);
8348 break;
8349 case SIPE_TRANSPORT_TCP:
8350 resolve_next_service(sip, service_tcp);
8351 break;
8356 static void sipe_connection_cleanup(struct sipe_account_data *sip)
8358 struct sipe_core_private *sipe_private = SIP_TO_CORE_PRIVATE;
8360 g_free(sip->epid);
8361 sip->epid = NULL;
8363 if (sip->srv_query_data != NULL)
8364 purple_srv_cancel(sip->srv_query_data);
8365 sip->srv_query_data = NULL;
8367 sipe_backend_transport_sip_disconnect(sipe_private->public.transport);
8368 g_free(sipe_private->server_name);
8369 sipe_private->server_name = NULL;
8371 sipe_auth_free(&sip->registrar);
8372 sipe_auth_free(&sip->proxy);
8374 g_free(sip->server_version);
8375 sip->server_version = NULL;
8377 if (sipe_private->timeouts) {
8378 GSList *entry = sipe_private->timeouts;
8379 while (entry) {
8380 struct scheduled_action *sched_action = entry->data;
8381 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action->name);
8382 purple_timeout_remove(sched_action->timeout_handler);
8383 if (sched_action->destroy) {
8384 (*sched_action->destroy)(sched_action->payload);
8386 g_free(sched_action->name);
8387 g_free(sched_action);
8388 entry = entry->next;
8391 g_slist_free(sipe_private->timeouts);
8393 if (sip->allow_events) {
8394 GSList *entry = sip->allow_events;
8395 while (entry) {
8396 g_free(entry->data);
8397 entry = entry->next;
8400 g_slist_free(sip->allow_events);
8402 if (sip->containers) {
8403 GSList *entry = sip->containers;
8404 while (entry) {
8405 free_container((struct sipe_container *)entry->data);
8406 entry = entry->next;
8409 g_slist_free(sip->containers);
8411 if (sip->contact)
8412 g_free(sip->contact);
8413 sip->contact = NULL;
8414 if (sip->regcallid)
8415 g_free(sip->regcallid);
8416 sip->regcallid = NULL;
8418 if (sip->focus_factory_uri)
8419 g_free(sip->focus_factory_uri);
8420 sip->focus_factory_uri = NULL;
8422 sip->processing_input = FALSE;
8424 if (sip->cal) {
8425 sipe_cal_calendar_free(sip->cal);
8427 sip->cal = NULL;
8431 * A callback for g_hash_table_foreach_remove
8433 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
8434 SIPE_UNUSED_PARAMETER gpointer user_data)
8436 sipe_free_buddy((struct sipe_buddy *) buddy);
8438 /* We must return TRUE as the key/value have already been deleted */
8439 return(TRUE);
8442 void sipe_core_deallocate(struct sipe_core_public *sipe_public)
8444 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
8445 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
8447 /* leave all conversations */
8448 sipe_session_close_all(sip);
8449 sipe_session_remove_all(sip);
8451 if (sip->csta) {
8452 sip_csta_close(sip);
8455 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
8456 /* unsubscribe all */
8457 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
8459 /* unregister */
8460 do_register_exp(sip, 0);
8463 sipe_connection_cleanup(sip);
8464 g_free(sipe_private->public.sip_name);
8465 g_free(sipe_private->public.sip_domain);
8466 g_free(sip->username);
8467 g_free(sip->email);
8468 g_free(sip->password);
8469 g_free(sip->authdomain);
8470 g_free(sip->authuser);
8471 g_free(sip->status);
8472 g_free(sip->note);
8473 g_free(sip->user_states);
8475 g_hash_table_foreach_steal(SIP_TO_CORE_PRIVATE->buddies, sipe_buddy_remove, NULL);
8476 g_hash_table_destroy(SIP_TO_CORE_PRIVATE->buddies);
8477 g_hash_table_destroy(sip->our_publications);
8478 g_hash_table_destroy(sip->user_state_publications);
8479 g_hash_table_destroy(sip->subscriptions);
8480 g_hash_table_destroy(sip->filetransfers);
8482 if (sip->groups) {
8483 GSList *entry = sip->groups;
8484 while (entry) {
8485 struct sipe_group *group = entry->data;
8486 g_free(group->name);
8487 g_free(group);
8488 entry = entry->next;
8491 g_slist_free(sip->groups);
8493 if (sip->our_publication_keys) {
8494 GSList *entry = sip->our_publication_keys;
8495 while (entry) {
8496 g_free(entry->data);
8497 entry = entry->next;
8500 g_slist_free(sip->our_publication_keys);
8502 while (sip->transactions)
8503 transactions_remove(sip, sip->transactions->data);
8504 g_free(sip);
8505 g_free(sipe_private);
8508 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
8509 SIPE_UNUSED_PARAMETER void *user_data)
8511 PurpleAccount *acct = purple_connection_get_account(gc);
8512 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
8513 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
8514 if (conv == NULL)
8515 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
8516 purple_conversation_present(conv);
8517 g_free(id);
8520 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
8521 SIPE_UNUSED_PARAMETER void *user_data)
8524 purple_blist_request_add_buddy(purple_connection_get_account(gc),
8525 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
8528 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
8529 SIPE_UNUSED_PARAMETER struct transaction *trans)
8531 PurpleNotifySearchResults *results;
8532 PurpleNotifySearchColumn *column;
8533 sipe_xml *searchResults;
8534 const sipe_xml *mrow;
8535 int match_count = 0;
8536 gboolean more = FALSE;
8537 gchar *secondary;
8539 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
8541 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
8542 if (!searchResults) {
8543 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
8544 return FALSE;
8547 results = purple_notify_searchresults_new();
8549 if (results == NULL) {
8550 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
8551 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
8553 sipe_xml_free(searchResults);
8554 return FALSE;
8557 column = purple_notify_searchresults_column_new(_("User name"));
8558 purple_notify_searchresults_column_add(results, column);
8560 column = purple_notify_searchresults_column_new(_("Name"));
8561 purple_notify_searchresults_column_add(results, column);
8563 column = purple_notify_searchresults_column_new(_("Company"));
8564 purple_notify_searchresults_column_add(results, column);
8566 column = purple_notify_searchresults_column_new(_("Country"));
8567 purple_notify_searchresults_column_add(results, column);
8569 column = purple_notify_searchresults_column_new(_("Email"));
8570 purple_notify_searchresults_column_add(results, column);
8572 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
8573 GList *row = NULL;
8575 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
8576 row = g_list_append(row, g_strdup(uri_parts[1]));
8577 g_strfreev(uri_parts);
8579 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
8580 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
8581 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
8582 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
8584 purple_notify_searchresults_row_add(results, row);
8585 match_count++;
8588 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
8589 char *data = sipe_xml_data(mrow);
8590 more = (g_strcasecmp(data, "true") == 0);
8591 g_free(data);
8594 secondary = g_strdup_printf(
8595 dngettext(PACKAGE_NAME,
8596 "Found %d contact%s:",
8597 "Found %d contacts%s:", match_count),
8598 match_count, more ? _(" (more matched your query)") : "");
8600 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
8601 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
8602 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
8604 g_free(secondary);
8605 sipe_xml_free(searchResults);
8606 return TRUE;
8609 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8611 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8612 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8613 unsigned i = 0;
8615 if (!attrs) return;
8617 do {
8618 PurpleRequestField *field = entries->data;
8619 const char *id = purple_request_field_get_id(field);
8620 const char *value = purple_request_field_string_get_value(field);
8622 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
8624 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8625 } while ((entries = g_list_next(entries)) != NULL);
8626 attrs[i] = NULL;
8628 if (i > 0) {
8629 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
8630 gchar *domain_uri = sip_uri_from_name(SIP_TO_CORE_PUBLIC->sip_domain);
8631 gchar *query = g_strjoinv(NULL, attrs);
8632 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8633 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
8634 send_soap_request_with_cb(sip, domain_uri, body,
8635 (TransCallback) process_search_contact_response, NULL);
8636 g_free(domain_uri);
8637 g_free(body);
8638 g_free(query);
8641 g_strfreev(attrs);
8644 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8645 gpointer value,
8646 GString* str)
8648 struct sipe_publication *publication = value;
8650 g_string_append_printf( str,
8651 SIPE_PUB_XML_PUBLICATION_CLEAR,
8652 publication->category,
8653 publication->instance,
8654 publication->container,
8655 publication->version,
8656 "static");
8659 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
8661 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
8662 if (sip->ocs2007) /* 2007+ */
8664 GString* str = g_string_new(NULL);
8665 gchar *publications;
8667 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8668 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
8669 return;
8672 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8673 publications = g_string_free(str, FALSE);
8675 send_presence_publish(sip, publications);
8676 g_free(publications);
8678 else /* 2005 */
8680 send_presence_soap0(sip, FALSE, TRUE);
8684 /** for Access levels menu */
8685 #define INDENT_FMT " %s"
8687 /** Member is directly placed to access level container.
8688 * For example SIP URI of user is in the container.
8690 #define INDENT_MARKED_FMT "* %s"
8692 /** Member is indirectly belong to access level container.
8693 * For example 'sameEnterprise' is in the container and user
8694 * belongs to that same enterprise.
8696 #define INDENT_MARKED_INHERITED_FMT "= %s"
8698 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
8699 const gchar *name,
8700 const gchar *status_name,
8701 gboolean is_online)
8703 gchar *note = NULL;
8704 gboolean is_oof_note = FALSE;
8705 gchar *activity = NULL;
8706 gchar *calendar = NULL;
8707 gchar *meeting_subject = NULL;
8708 gchar *meeting_location = NULL;
8709 gchar *access_text = NULL;
8710 GSList *info = NULL;
8712 #define SIPE_ADD_BUDDY_INFO(l, t) \
8714 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
8715 sbi->label = (l); \
8716 sbi->text = (t); \
8717 info = g_slist_append(info, sbi); \
8720 if (sipe_public) { //happens on pidgin exit
8721 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
8722 struct sipe_buddy *sbuddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, name);
8723 if (sbuddy) {
8724 note = sbuddy->note;
8725 is_oof_note = sbuddy->is_oof_note;
8726 activity = sbuddy->activity;
8727 calendar = sipe_cal_get_description(sbuddy);
8728 meeting_subject = sbuddy->meeting_subject;
8729 meeting_location = sbuddy->meeting_location;
8731 if (sip->ocs2007) {
8732 gboolean is_group_access = FALSE;
8733 const int container_id = sipe_find_access_level(sip, "user", sipe_get_no_sip_uri(name), &is_group_access);
8734 const char *access_level = sipe_get_access_level_name(container_id);
8735 access_text = is_group_access ?
8736 g_strdup(access_level) :
8737 g_strdup_printf(INDENT_MARKED_FMT, access_level);
8741 //Layout
8742 if (is_online)
8744 gchar *status_str = g_strdup(activity ? activity : status_name);
8746 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
8748 if (is_online && !is_empty(calendar))
8750 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
8751 calendar = NULL;
8753 g_free(calendar);
8754 if (!is_empty(meeting_location))
8756 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
8758 if (!is_empty(meeting_subject))
8760 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
8762 if (note)
8764 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
8765 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
8766 g_strdup_printf("<i>%s</i>", note));
8768 if (access_text) {
8769 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
8772 return(info);
8775 static PurpleBuddy *
8776 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8778 PurpleBuddy *clone;
8779 const gchar *server_alias, *email;
8780 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8782 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8784 purple_blist_add_buddy(clone, NULL, group, NULL);
8786 server_alias = purple_buddy_get_server_alias(buddy);
8787 if (server_alias) {
8788 purple_blist_server_alias_buddy(clone, server_alias);
8791 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8792 if (email) {
8793 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8796 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8797 //for UI to update;
8798 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8799 return clone;
8802 static void
8803 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8805 PurpleBuddy *buddy, *b;
8806 PurpleConnection *gc;
8807 PurpleGroup * group = purple_find_group(group_name);
8809 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8811 buddy = (PurpleBuddy *)node;
8813 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
8814 gc = purple_account_get_connection(buddy->account);
8816 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8817 if (!b){
8818 purple_blist_add_buddy_clone(group, buddy);
8821 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8824 static void
8825 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8827 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
8829 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
8831 /* 2007+ conference */
8832 if (sip->ocs2007)
8834 sipe_conf_add(sip, buddy->name);
8836 else /* 2005- multiparty chat */
8838 gchar *self = sip_uri_self(sip);
8839 struct sip_session *session;
8841 session = sipe_session_add_chat(sip);
8842 session->chat_title = sipe_chat_get_name(session->callid);
8843 session->roster_manager = g_strdup(self);
8845 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8846 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8847 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8848 sipe_invite(sip, session, buddy->name, NULL, NULL, NULL, FALSE);
8850 g_free(self);
8854 static gboolean
8855 sipe_is_election_finished(struct sip_session *session)
8857 gboolean res = TRUE;
8859 SIPE_DIALOG_FOREACH {
8860 if (dialog->election_vote == 0) {
8861 res = FALSE;
8862 break;
8864 } SIPE_DIALOG_FOREACH_END;
8866 if (res) {
8867 session->is_voting_in_progress = FALSE;
8869 return res;
8872 static void
8873 sipe_election_start(struct sipe_account_data *sip,
8874 struct sip_session *session)
8876 int election_timeout;
8878 if (session->is_voting_in_progress) {
8879 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
8880 return;
8881 } else {
8882 session->is_voting_in_progress = TRUE;
8884 session->bid = rand();
8886 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
8888 SIPE_DIALOG_FOREACH {
8889 /* reset election_vote for each chat participant */
8890 dialog->election_vote = 0;
8892 /* send RequestRM to each chat participant*/
8893 sipe_send_election_request_rm(sip, dialog, session->bid);
8894 } SIPE_DIALOG_FOREACH_END;
8896 election_timeout = 15; /* sec */
8897 sipe_schedule_action("<+election-result>",
8898 election_timeout,
8899 sipe_election_result,
8900 NULL,
8901 SIP_TO_CORE_PRIVATE,
8902 session);
8906 * @param who a URI to whom to invite to chat
8908 void
8909 sipe_invite_to_chat(struct sipe_account_data *sip,
8910 struct sip_session *session,
8911 const gchar *who)
8913 /* a conference */
8914 if (session->focus_uri)
8916 sipe_invite_conf(sip, session, who);
8918 else /* a multi-party chat */
8920 gchar *self = sip_uri_self(sip);
8921 if (session->roster_manager) {
8922 if (sipe_strcase_equal(session->roster_manager, self)) {
8923 sipe_invite(sip, session, who, NULL, NULL, NULL, FALSE);
8924 } else {
8925 sipe_refer(sip, session, who);
8927 } else {
8928 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
8930 session->pending_invite_queue = slist_insert_unique_sorted(
8931 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
8933 sipe_election_start(sip, session);
8935 g_free(self);
8939 void
8940 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
8941 struct sip_session *session)
8943 gchar *invitee;
8944 GSList *entry = session->pending_invite_queue;
8946 while (entry) {
8947 invitee = entry->data;
8948 sipe_invite_to_chat(sip, session, invitee);
8949 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
8950 g_free(invitee);
8954 static void
8955 sipe_election_result(struct sipe_core_private *sipe_private,
8956 void *sess)
8958 struct sipe_account_data *sip = sipe_private->temporary;
8959 struct sip_session *session = (struct sip_session *)sess;
8960 gchar *rival;
8961 gboolean has_won = TRUE;
8963 if (session->roster_manager) {
8964 SIPE_DEBUG_INFO(
8965 "sipe_election_result: RM has already been elected in the meantime. It is %s",
8966 session->roster_manager);
8967 return;
8970 session->is_voting_in_progress = FALSE;
8972 SIPE_DIALOG_FOREACH {
8973 if (dialog->election_vote < 0) {
8974 has_won = FALSE;
8975 rival = dialog->with;
8976 break;
8978 } SIPE_DIALOG_FOREACH_END;
8980 if (has_won) {
8981 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
8983 session->roster_manager = sip_uri_self(sip);
8985 SIPE_DIALOG_FOREACH {
8986 /* send SetRM to each chat participant*/
8987 sipe_send_election_set_rm(sip, dialog);
8988 } SIPE_DIALOG_FOREACH_END;
8989 } else {
8990 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
8992 session->bid = 0;
8994 sipe_process_pending_invite_queue(sip, session);
8998 * For 2007+ conference only.
9000 static void
9001 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
9003 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9004 struct sip_session *session;
9006 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
9007 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
9009 session = sipe_session_find_chat_by_title(sip, chat_title);
9011 sipe_conf_modify_user_role(sip, session, buddy->name);
9015 * For 2007+ conference only.
9017 static void
9018 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
9020 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9021 struct sip_session *session;
9023 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
9024 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
9026 session = sipe_session_find_chat_by_title(sip, chat_title);
9028 sipe_conf_delete_user(sip, session, buddy->name);
9031 static void
9032 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
9034 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9035 struct sip_session *session;
9037 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
9038 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
9040 session = sipe_session_find_chat_by_title(sip, chat_title);
9042 sipe_invite_to_chat(sip, session, buddy->name);
9045 static void
9046 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
9048 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9050 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
9051 if (phone) {
9052 char *tel_uri = sip_to_tel_uri(phone);
9054 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
9055 sip_csta_make_call(sip, tel_uri);
9057 g_free(tel_uri);
9061 static void
9062 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
9064 /** Translators: replace with URL to localized page
9065 * If it doesn't exist copy the original URL */
9066 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
9069 static void
9070 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
9072 const gchar *email;
9073 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
9075 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9076 if (email)
9078 char *command_line = g_strdup_printf(
9079 #ifdef _WIN32
9080 "cmd /c start"
9081 #else
9082 "xdg-email"
9083 #endif
9084 " mailto:%s", email);
9085 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
9087 g_spawn_command_line_async(command_line, NULL);
9088 g_free(command_line);
9090 else
9092 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
9096 static void
9097 sipe_buddy_menu_access_level_cb(SIPE_UNUSED_PARAMETER PurpleBuddy *buddy,
9098 struct sipe_container *container)
9100 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9101 struct sipe_container_member *member;
9103 if (!container || !container->members) return;
9105 member = ((struct sipe_container_member *)container->members->data);
9107 if (!member->type) return;
9109 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
9110 container->id, member->type, member->value ? member->value : "");
9112 sipe_change_access_level(sip, container->id, member->type, member->value);
9115 static GList *
9116 sipe_get_access_control_menu(struct sipe_account_data *sip,
9117 const char* uri);
9120 * A menu which appear when right-clicking on buddy in contact list.
9122 GList *
9123 sipe_buddy_menu(PurpleBuddy *buddy)
9125 PurpleBlistNode *g_node;
9126 PurpleGroup *group, *gr_parent;
9127 PurpleMenuAction *act;
9128 GList *menu = NULL;
9129 GList *menu_groups = NULL;
9130 struct sipe_account_data *sip = PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA;
9131 const char *email;
9132 const char *phone;
9133 const char *phone_disp_str;
9134 gchar *self = sip_uri_self(sip);
9136 SIPE_SESSION_FOREACH {
9137 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
9139 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
9141 PurpleConvChatBuddyFlags flags;
9142 PurpleConvChatBuddyFlags flags_us;
9144 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
9145 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9146 if (session->focus_uri
9147 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
9148 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9150 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
9151 act = purple_menu_action_new(label,
9152 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
9153 session->chat_title, NULL);
9154 g_free(label);
9155 menu = g_list_prepend(menu, act);
9158 if (session->focus_uri
9159 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9161 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
9162 act = purple_menu_action_new(label,
9163 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
9164 session->chat_title, NULL);
9165 g_free(label);
9166 menu = g_list_prepend(menu, act);
9169 else
9171 if (!session->focus_uri
9172 || (session->focus_uri && !session->locked))
9174 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
9175 act = purple_menu_action_new(label,
9176 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
9177 session->chat_title, NULL);
9178 g_free(label);
9179 menu = g_list_prepend(menu, act);
9183 } SIPE_SESSION_FOREACH_END;
9185 act = purple_menu_action_new(_("New chat"),
9186 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
9187 NULL, NULL);
9188 menu = g_list_prepend(menu, act);
9190 if (sip->csta && !sip->csta->line_status) {
9191 gchar *tmp = NULL;
9192 /* work phone */
9193 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
9194 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
9195 if (phone) {
9196 gchar *label = g_strdup_printf(_("Work %s"),
9197 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9198 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9199 g_free(tmp);
9200 tmp = NULL;
9201 g_free(label);
9202 menu = g_list_prepend(menu, act);
9205 /* mobile phone */
9206 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
9207 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
9208 if (phone) {
9209 gchar *label = g_strdup_printf(_("Mobile %s"),
9210 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9211 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9212 g_free(tmp);
9213 tmp = NULL;
9214 g_free(label);
9215 menu = g_list_prepend(menu, act);
9218 /* home phone */
9219 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
9220 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
9221 if (phone) {
9222 gchar *label = g_strdup_printf(_("Home %s"),
9223 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9224 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9225 g_free(tmp);
9226 tmp = NULL;
9227 g_free(label);
9228 menu = g_list_prepend(menu, act);
9231 /* other phone */
9232 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
9233 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
9234 if (phone) {
9235 gchar *label = g_strdup_printf(_("Other %s"),
9236 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9237 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9238 g_free(tmp);
9239 tmp = NULL;
9240 g_free(label);
9241 menu = g_list_prepend(menu, act);
9244 /* custom1 phone */
9245 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
9246 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
9247 if (phone) {
9248 gchar *label = g_strdup_printf(_("Custom1 %s"),
9249 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
9250 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
9251 g_free(tmp);
9252 tmp = NULL;
9253 g_free(label);
9254 menu = g_list_prepend(menu, act);
9258 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
9259 if (email) {
9260 act = purple_menu_action_new(_("Send email..."),
9261 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
9262 NULL, NULL);
9263 menu = g_list_prepend(menu, act);
9266 /* Access Level */
9267 if (sip->ocs2007) {
9268 GList *menu_access_levels = sipe_get_access_control_menu(sip, buddy->name);
9270 act = purple_menu_action_new(_("Access level"),
9271 NULL,
9272 NULL, menu_access_levels);
9273 menu = g_list_prepend(menu, act);
9276 /* Copy to */
9277 gr_parent = purple_buddy_get_group(buddy);
9278 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
9279 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
9280 continue;
9282 group = (PurpleGroup *)g_node;
9283 if (group == gr_parent)
9284 continue;
9286 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
9287 continue;
9289 act = purple_menu_action_new(purple_group_get_name(group),
9290 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
9291 group->name, NULL);
9292 menu_groups = g_list_prepend(menu_groups, act);
9294 menu_groups = g_list_reverse(menu_groups);
9296 act = purple_menu_action_new(_("Copy to"),
9297 NULL,
9298 NULL, menu_groups);
9299 menu = g_list_prepend(menu, act);
9301 menu = g_list_reverse(menu);
9303 g_free(self);
9304 return menu;
9307 static void
9308 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
9310 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
9311 const char *domain = purple_request_fields_get_string(fields, "access_domain");
9312 int index = purple_request_fields_get_choice(fields, "container_id");
9313 /* move Blocked first */
9314 int i = (index == 4) ? 0 : index + 1;
9315 int container_id = containers[i];
9317 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
9319 sipe_change_access_level(sip, container_id, "domain", domain);
9322 static void
9323 sipe_ask_access_domain(struct sipe_account_data *sip)
9325 PurpleAccount *account = sip->account;
9326 PurpleConnection *gc = sip->gc;
9327 PurpleRequestFields *fields;
9328 PurpleRequestFieldGroup *g;
9329 PurpleRequestField *f;
9331 fields = purple_request_fields_new();
9333 g = purple_request_field_group_new(NULL);
9334 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
9335 purple_request_field_set_required(f, TRUE);
9336 purple_request_field_group_add_field(g, f);
9338 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
9339 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
9340 purple_request_field_choice_add(f, _("Team"));
9341 purple_request_field_choice_add(f, _("Company"));
9342 purple_request_field_choice_add(f, _("Public"));
9343 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
9344 purple_request_field_choice_set_default_value(f, 3); /* index */
9345 purple_request_field_set_required(f, TRUE);
9346 purple_request_field_group_add_field(g, f);
9348 purple_request_fields_add_group(fields, g);
9350 purple_request_fields(gc, _("Add new domain"),
9351 _("Add new domain"), NULL, fields,
9352 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
9353 _("Cancel"), NULL,
9354 account, NULL, NULL, gc);
9357 static void
9358 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
9360 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_ACCOUNT_DATA);
9363 static GList *
9364 sipe_get_access_levels_menu(struct sipe_account_data *sip,
9365 const char* member_type,
9366 const char* member_value,
9367 const gboolean extra_menu)
9369 GList *menu_access_levels = NULL;
9370 unsigned int i;
9371 char *menu_name;
9372 PurpleMenuAction *act;
9373 struct sipe_container *container;
9374 struct sipe_container_member *member;
9375 gboolean is_group_access = FALSE;
9376 int container_id = sipe_find_access_level(sip, member_type, member_value, &is_group_access);
9378 for (i = 1; i <= CONTAINERS_LEN; i++) {
9379 /* to put Blocked level last in menu list.
9380 * Blocked should remaim in the first place in the containers[] array.
9382 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
9383 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
9385 container = g_new0(struct sipe_container, 1);
9386 member = g_new0(struct sipe_container_member, 1);
9387 container->id = containers[j];
9388 container->members = g_slist_append(container->members, member);
9389 member->type = g_strdup(member_type);
9390 member->value = g_strdup(member_value);
9392 /* current container/access level */
9393 if (((int)containers[j]) == container_id) {
9394 menu_name = is_group_access ?
9395 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
9396 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
9397 } else {
9398 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
9401 act = purple_menu_action_new(menu_name,
9402 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
9403 container, NULL);
9404 g_free(menu_name);
9405 menu_access_levels = g_list_prepend(menu_access_levels, act);
9408 if (extra_menu && (container_id >= 0)) {
9409 /* separator */
9410 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
9411 menu_access_levels = g_list_prepend(menu_access_levels, act);
9413 if (!is_group_access) {
9414 container = g_new0(struct sipe_container, 1);
9415 member = g_new0(struct sipe_container_member, 1);
9416 container->id = -1;
9417 container->members = g_slist_append(container->members, member);
9418 member->type = g_strdup(member_type);
9419 member->value = g_strdup(member_value);
9421 /* Translators: remove (clear) previously assigned access level */
9422 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
9423 act = purple_menu_action_new(menu_name,
9424 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
9425 container, NULL);
9426 g_free(menu_name);
9427 menu_access_levels = g_list_prepend(menu_access_levels, act);
9431 menu_access_levels = g_list_reverse(menu_access_levels);
9432 return menu_access_levels;
9435 static GList *
9436 sipe_get_access_groups_menu(struct sipe_account_data *sip)
9438 GList *menu_access_groups = NULL;
9439 PurpleMenuAction *act;
9440 GSList *access_domains = NULL;
9441 GSList *entry;
9442 char *menu_name;
9443 char *domain;
9445 act = purple_menu_action_new(_("People in my company"),
9446 NULL,
9447 NULL, sipe_get_access_levels_menu(sip, "sameEnterprise", NULL, FALSE));
9448 menu_access_groups = g_list_prepend(menu_access_groups, act);
9450 /* this is original name, don't edit */
9451 act = purple_menu_action_new(_("People in domains connected with my company"),
9452 NULL,
9453 NULL, sipe_get_access_levels_menu(sip, "federated", NULL, FALSE));
9454 menu_access_groups = g_list_prepend(menu_access_groups, act);
9456 act = purple_menu_action_new(_("People in public domains"),
9457 NULL,
9458 NULL, sipe_get_access_levels_menu(sip, "publicCloud", NULL, TRUE));
9459 menu_access_groups = g_list_prepend(menu_access_groups, act);
9461 access_domains = sipe_get_access_domains(sip);
9462 entry = access_domains;
9463 while (entry) {
9464 domain = entry->data;
9466 menu_name = g_strdup_printf(_("People at %s"), domain);
9467 act = purple_menu_action_new(menu_name,
9468 NULL,
9469 NULL, sipe_get_access_levels_menu(sip, "domain", g_strdup(domain), TRUE));
9470 menu_access_groups = g_list_prepend(menu_access_groups, act);
9471 g_free(menu_name);
9473 entry = entry->next;
9476 /* separator */
9477 /* People in domains connected with my company */
9478 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
9479 menu_access_groups = g_list_prepend(menu_access_groups, act);
9481 act = purple_menu_action_new(_("Add new domain..."),
9482 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
9483 NULL, NULL);
9484 menu_access_groups = g_list_prepend(menu_access_groups, act);
9486 menu_access_groups = g_list_reverse(menu_access_groups);
9488 return menu_access_groups;
9491 static GList *
9492 sipe_get_access_control_menu(struct sipe_account_data *sip,
9493 const char* uri)
9495 GList *menu_access_levels = NULL;
9496 GList *menu_access_groups = NULL;
9497 char *menu_name;
9498 PurpleMenuAction *act;
9500 menu_access_levels = sipe_get_access_levels_menu(sip, "user", sipe_get_no_sip_uri(uri), TRUE);
9502 menu_access_groups = sipe_get_access_groups_menu(sip);
9504 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
9505 act = purple_menu_action_new(menu_name,
9506 NULL,
9507 NULL, menu_access_groups);
9508 g_free(menu_name);
9509 menu_access_levels = g_list_append(menu_access_levels, act);
9511 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
9512 act = purple_menu_action_new(menu_name,
9513 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
9514 NULL, NULL);
9515 g_free(menu_name);
9516 menu_access_levels = g_list_append(menu_access_levels, act);
9518 return menu_access_levels;
9521 static void
9522 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
9524 struct sipe_account_data *sip = PURPLE_CHAT_TO_SIPE_ACCOUNT_DATA;
9525 struct sip_session *session;
9527 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9528 sipe_conf_modify_conference_lock(sip, session, locked);
9531 static void
9532 sipe_chat_menu_unlock_cb(PurpleChat *chat)
9534 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
9535 sipe_conf_modify_lock(chat, FALSE);
9538 static void
9539 sipe_chat_menu_lock_cb(PurpleChat *chat)
9541 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
9542 sipe_conf_modify_lock(chat, TRUE);
9545 GList *
9546 sipe_chat_menu(PurpleChat *chat)
9548 PurpleMenuAction *act;
9549 PurpleConvChatBuddyFlags flags_us;
9550 GList *menu = NULL;
9551 struct sipe_account_data *sip = PURPLE_CHAT_TO_SIPE_ACCOUNT_DATA;
9552 struct sip_session *session;
9553 gchar *self;
9555 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
9556 if (!session) return NULL;
9558 self = sip_uri_self(sip);
9559 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
9561 if (session->focus_uri
9562 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
9564 if (session->locked) {
9565 act = purple_menu_action_new(_("Unlock"),
9566 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
9567 NULL, NULL);
9568 menu = g_list_prepend(menu, act);
9569 } else {
9570 act = purple_menu_action_new(_("Lock"),
9571 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
9572 NULL, NULL);
9573 menu = g_list_prepend(menu, act);
9577 menu = g_list_reverse(menu);
9579 g_free(self);
9580 return menu;
9583 static gboolean
9584 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
9586 char *uri = trans->payload->data;
9588 PurpleNotifyUserInfo *info;
9589 PurpleBuddy *pbuddy = NULL;
9590 struct sipe_buddy *sbuddy;
9591 const char *alias = NULL;
9592 char *device_name = NULL;
9593 char *server_alias = NULL;
9594 char *phone_number = NULL;
9595 char *email = NULL;
9596 const char *site;
9597 char *first_name = NULL;
9598 char *last_name = NULL;
9600 if (!sip) return FALSE;
9602 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sip->username);
9604 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9605 alias = purple_buddy_get_local_alias(pbuddy);
9607 //will query buddy UA's capabilities and send answer to log
9608 sipe_options_request(sip, uri);
9610 sbuddy = g_hash_table_lookup(SIP_TO_CORE_PRIVATE->buddies, uri);
9611 if (sbuddy) {
9612 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9615 info = purple_notify_user_info_new();
9617 if (msg->response != 200) {
9618 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg->response);
9619 } else {
9620 sipe_xml *searchResults;
9621 const sipe_xml *mrow;
9623 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
9624 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
9625 if (!searchResults) {
9626 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
9627 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
9628 const char *value;
9629 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
9630 email = g_strdup(sipe_xml_attribute(mrow, "email"));
9631 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
9633 /* For 2007 system we will take this from ContactCard -
9634 * it has cleaner tel: URIs at least
9636 if (!sip->ocs2007) {
9637 char *tel_uri = sip_to_tel_uri(phone_number);
9638 /* trims its parameters, so call first */
9639 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
9640 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
9641 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
9642 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
9643 g_free(tel_uri);
9646 if (server_alias && strlen(server_alias) > 0) {
9647 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9649 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
9650 purple_notify_user_info_add_pair(info, _("Job title"), value);
9652 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
9653 purple_notify_user_info_add_pair(info, _("Office"), value);
9655 if (phone_number && strlen(phone_number) > 0) {
9656 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9658 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
9659 purple_notify_user_info_add_pair(info, _("Company"), value);
9661 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
9662 purple_notify_user_info_add_pair(info, _("City"), value);
9664 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
9665 purple_notify_user_info_add_pair(info, _("State"), value);
9667 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
9668 purple_notify_user_info_add_pair(info, _("Country"), value);
9670 if (email && strlen(email) > 0) {
9671 purple_notify_user_info_add_pair(info, _("Email address"), email);
9675 sipe_xml_free(searchResults);
9678 purple_notify_user_info_add_section_break(info);
9680 if (is_empty(server_alias)) {
9681 g_free(server_alias);
9682 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9683 if (server_alias) {
9684 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9688 /* present alias if it differs from server alias */
9689 if (alias && !sipe_strequal(alias, server_alias))
9691 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9694 if (is_empty(email)) {
9695 g_free(email);
9696 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9697 if (email) {
9698 purple_notify_user_info_add_pair(info, _("Email address"), email);
9702 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9703 if (site) {
9704 purple_notify_user_info_add_pair(info, _("Site"), site);
9707 sipe_get_first_last_names(sip, uri, &first_name, &last_name);
9708 if (first_name && last_name) {
9709 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9711 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9712 g_free(link);
9714 g_free(first_name);
9715 g_free(last_name);
9717 if (device_name) {
9718 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9721 /* show a buddy's user info in a nice dialog box */
9722 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9723 uri, /* buddy's URI */
9724 info, /* body */
9725 NULL, /* callback called when dialog closed */
9726 NULL); /* userdata for callback */
9728 g_free(phone_number);
9729 g_free(server_alias);
9730 g_free(email);
9731 g_free(device_name);
9733 return TRUE;
9737 * AD search first, LDAP based
9739 void sipe_get_info(PurpleConnection *gc, const char *username)
9741 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
9742 gchar *domain_uri = sip_uri_from_name(SIP_TO_CORE_PUBLIC->sip_domain);
9743 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9744 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9745 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9747 payload->destroy = g_free;
9748 payload->data = g_strdup(username);
9750 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
9751 send_soap_request_with_cb(sip, domain_uri, body,
9752 (TransCallback) process_get_info_response, payload);
9753 g_free(domain_uri);
9754 g_free(body);
9755 g_free(row);
9759 Local Variables:
9760 mode: c
9761 c-file-style: "bsd"
9762 indent-tabs-mode: t
9763 tab-width: 8
9764 End: