core cleanup: convert boolean ocs2007 to a flag
[siplcs.git] / src / core / sipe.c
blob9fac02fc134cfc092a90c681957cf93114458598
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 #include <time.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <unistd.h>
47 #include <glib.h>
49 #include "sipe-common.h"
51 #include "account.h"
52 #include "blist.h"
53 #include "connection.h"
54 #include "conversation.h"
55 #include "ft.h"
56 #include "notify.h"
57 #include "plugin.h"
58 #include "privacy.h"
59 #include "request.h"
60 #include "savedstatuses.h"
62 #include "core-depurple.h" /* Temporary for the core de-purple transition */
64 #include "http-conn.h"
65 #include "sipmsg.h"
66 #include "sip-csta.h"
67 #include "sip-sec.h"
68 #include "sip-transport.h"
69 #include "sipe-backend.h"
70 #include "sipe-buddy.h"
71 #include "sipe-cal.h"
72 #include "sipe-chat.h"
73 #include "sipe-conf.h"
74 #include "sipe-core.h"
75 #include "sipe-core-private.h"
76 #include "sipe-dialog.h"
77 #include "sipe-digest.h"
78 #include "sipe-ews.h"
79 #include "sipe-domino.h"
80 #include "sipe-ft.h"
81 #include "sipe-mime.h"
82 #include "sipe-nls.h"
83 #include "sipe-schedule.h"
84 #include "sipe-session.h"
85 #include "sipe-media.h"
86 #include "sipe-sign.h"
87 #include "sipe-utils.h"
88 #include "sipe-xml.h"
89 #include "uuid.h"
90 #include "sipe.h"
92 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
94 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
95 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
97 /* Status identifiers (see also: sipe_status_types()) */
98 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
99 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
100 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
101 /* PURPLE_STATUS_UNAVAILABLE: */
102 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
103 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
104 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
105 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
106 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
107 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
108 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
109 /* PURPLE_STATUS_AWAY: */
110 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
111 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
112 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
113 /** Reuters status (user settable) */
114 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
115 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
116 /* ??? PURPLE_STATUS_MOBILE */
117 /* ??? PURPLE_STATUS_TUNE */
119 /* Status attributes (see also sipe_status_types() */
120 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
122 #ifdef HAVE_GMIME
123 /* pls. don't add multipart/related - it's not used in IM modality */
124 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
125 #else
126 /* this is a rediculous hack as Pidgin's MIME implementastion doesn't support (or have bug) in multipart/alternative */
127 /* OCS/OC won't use multipart/related so we don't advertase it */
128 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
129 #endif
131 static struct sipe_activity_map_struct
133 sipe_activity type;
134 const char *token;
135 const char *desc;
136 const char *status_id;
138 } const sipe_activity_map[] =
140 /* This has nothing to do with Availability numbers, like 3500 (online).
141 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
143 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
144 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
145 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
146 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
147 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
148 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
149 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
150 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
151 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
152 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
153 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
154 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
155 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
156 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
157 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
159 /** @param x is sipe_activity */
160 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
163 /* Action name templates */
164 #define ACTION_NAME_PRESENCE "<presence><%s>"
166 static sipe_activity
167 sipe_get_activity_by_token(const char *token)
169 int i;
171 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
173 if (sipe_strequal(token, sipe_activity_map[i].token))
174 return sipe_activity_map[i].type;
177 return sipe_activity_map[0].type;
180 static const char *
181 sipe_get_activity_desc_by_token(const char *token)
183 if (!token) return NULL;
185 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
188 /** Allows to send typed messages from chat window again after account reinstantiation. */
189 static void
190 sipe_rejoin_chat(PurpleConversation *conv)
192 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
193 PURPLE_CONV_CHAT(conv)->left)
195 PURPLE_CONV_CHAT(conv)->left = FALSE;
196 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
200 static void send_presence_status(struct sipe_core_private *sipe_private,
201 void *unused);
203 static void sipe_auth_free(struct sip_auth *auth)
205 g_free(auth->opaque);
206 auth->opaque = NULL;
207 g_free(auth->realm);
208 auth->realm = NULL;
209 g_free(auth->target);
210 auth->target = NULL;
211 auth->version = 0;
212 auth->type = AUTH_TYPE_UNSET;
213 auth->retries = 0;
214 auth->expires = 0;
215 g_free(auth->gssapi_data);
216 auth->gssapi_data = NULL;
217 sip_sec_destroy_context(auth->gssapi_context);
218 auth->gssapi_context = NULL;
221 void
222 sipe_make_signature(struct sipe_core_private *sipe_private,
223 struct sipmsg *msg)
225 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
226 if (sip->registrar.gssapi_context) {
227 struct sipmsg_breakdown msgbd;
228 gchar *signature_input_str;
229 msgbd.msg = msg;
230 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
231 msgbd.rand = g_strdup_printf("%08x", g_random_int());
232 sip->registrar.ntlm_num++;
233 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
234 signature_input_str = sipmsg_breakdown_get_string(sip->registrar.version, &msgbd);
235 if (signature_input_str != NULL) {
236 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
237 msg->signature = signature_hex;
238 msg->rand = g_strdup(msgbd.rand);
239 msg->num = g_strdup(msgbd.num);
240 g_free(signature_input_str);
242 sipmsg_breakdown_free(&msgbd);
246 gchar *auth_header(struct sipe_core_private *sipe_private,
247 struct sip_auth *auth, struct sipmsg * msg)
249 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
250 const char *authuser = sip->authuser;
251 gchar *ret;
253 if (!authuser || strlen(authuser) < 1) {
254 authuser = sipe_private->username;
257 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
258 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
259 gchar *version_str;
261 // If we have a signature for the message, include that
262 if (msg->signature) {
263 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);
266 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
267 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
268 gchar *gssapi_data;
269 gchar *opaque;
270 gchar *sign_str = NULL;
272 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
273 &(auth->expires),
274 auth->type,
275 SIPE_CORE_PUBLIC_FLAG_IS(SSO),
276 sip->authdomain ? sip->authdomain : "",
277 authuser,
278 sip->password,
279 auth->target,
280 auth->gssapi_data);
281 if (!gssapi_data || !auth->gssapi_context) {
282 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
283 SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
284 _("Failed to authenticate to server"));
285 return NULL;
288 if (auth->version > 3) {
289 sipe_make_signature(sipe_private, msg);
290 sign_str = g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
291 msg->rand, msg->num, msg->signature);
292 } else {
293 sign_str = g_strdup("");
296 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
297 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
298 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);
299 g_free(opaque);
300 g_free(gssapi_data);
301 g_free(version_str);
302 g_free(sign_str);
303 return ret;
306 version_str = auth->version > 2 ? g_strdup_printf(", version=%d", auth->version) : g_strdup("");
307 ret = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol, auth->realm, auth->target, version_str);
308 g_free(version_str);
309 return ret;
311 } else { /* Digest */
312 gchar *string;
313 gchar *hex_digest;
314 guchar digest[SIPE_DIGEST_MD5_LENGTH];
316 /* Calculate new session key */
317 if (!auth->opaque) {
318 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth->gssapi_data, auth->realm);
319 if (sip->password) {
321 * Calculate a session key for HTTP MD5 Digest authentation
323 * See RFC 2617 for more information.
325 string = g_strdup_printf("%s:%s:%s",
326 authuser,
327 auth->realm,
328 sip->password);
329 sipe_digest_md5((guchar *)string, strlen(string), digest);
330 g_free(string);
331 auth->opaque = buff_to_hex_str(digest, sizeof(digest));
336 * Calculate a response for HTTP MD5 Digest authentication
338 * See RFC 2617 for more information.
340 string = g_strdup_printf("%s:%s", msg->method, msg->target);
341 sipe_digest_md5((guchar *)string, strlen(string), digest);
342 g_free(string);
344 hex_digest = buff_to_hex_str(digest, sizeof(digest));
345 string = g_strdup_printf("%s:%s:%s", auth->opaque, auth->gssapi_data, hex_digest);
346 g_free(hex_digest);
347 sipe_digest_md5((guchar *)string, strlen(string), digest);
348 g_free(string);
350 hex_digest = buff_to_hex_str(digest, sizeof(digest));
351 SIPE_DEBUG_INFO("Digest response %s", hex_digest);
352 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);
353 g_free(hex_digest);
354 return ret;
358 static char *parse_attribute(const char *attrname, const char *source)
360 const char *tmp, *tmp2;
361 char *retval = NULL;
362 int len = strlen(attrname);
364 if (g_str_has_prefix(source, attrname)) {
365 tmp = source + len;
366 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
367 if (tmp2)
368 retval = g_strndup(tmp, tmp2 - tmp);
369 else
370 retval = g_strdup(tmp);
373 return retval;
376 static void fill_auth(const gchar *hdr, struct sip_auth *auth)
378 int i;
379 gchar **parts;
381 if (!hdr) {
382 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
383 return;
386 if (!g_strncasecmp(hdr, "NTLM", 4)) {
387 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
388 auth->type = AUTH_TYPE_NTLM;
389 hdr += 5;
390 auth->nc = 1;
391 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
392 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
393 auth->type = AUTH_TYPE_KERBEROS;
394 hdr += 9;
395 auth->nc = 3;
396 } else {
397 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
398 auth->type = AUTH_TYPE_DIGEST;
399 hdr += 7;
402 parts = g_strsplit(hdr, "\", ", 0);
403 for (i = 0; parts[i]; i++) {
404 char *tmp;
406 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
408 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
409 g_free(auth->gssapi_data);
410 auth->gssapi_data = tmp;
412 if (auth->type == AUTH_TYPE_NTLM) {
413 /* NTLM module extracts nonce from gssapi-data */
414 auth->nc = 3;
417 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
418 /* Only used with AUTH_TYPE_DIGEST */
419 g_free(auth->gssapi_data);
420 auth->gssapi_data = tmp;
421 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
422 g_free(auth->opaque);
423 auth->opaque = tmp;
424 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
425 g_free(auth->realm);
426 auth->realm = tmp;
428 if (auth->type == AUTH_TYPE_DIGEST) {
429 /* Throw away old session key */
430 g_free(auth->opaque);
431 auth->opaque = NULL;
432 auth->nc = 1;
434 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
435 g_free(auth->target);
436 auth->target = tmp;
437 } else if ((tmp = parse_attribute("version=", parts[i]))) {
438 auth->version = atoi(tmp);
439 g_free(tmp);
441 // uncomment to revert to previous functionality if version 3+ does not work.
442 // auth->version = 2;
444 g_strfreev(parts);
446 return;
450 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
452 static void
453 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
454 gchar *from0,
455 gchar *body,
456 TransCallback callback,
457 struct transaction_payload *payload)
459 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
460 gchar *contact = get_contact(sipe_private);
461 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
462 "Content-Type: application/SOAP+xml\r\n",contact);
464 struct transaction *trans = send_sip_request(sipe_private, "SERVICE", from, from, hdr, body, NULL, callback);
465 trans->payload = payload;
467 g_free(from);
468 g_free(contact);
469 g_free(hdr);
472 static void send_soap_request(struct sipe_core_private *sipe_private,
473 gchar *body)
475 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
479 * Returns pointer to URI without sip: prefix if any
481 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
482 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
484 * Doesn't allocate memory
486 static const char *
487 sipe_get_no_sip_uri(const char *sip_uri)
489 const char *prefix = "sip:";
490 if (!sip_uri) return NULL;
492 if (g_str_has_prefix(sip_uri, prefix)) {
493 return (sip_uri+strlen(prefix));
494 } else {
495 return sip_uri;
499 static void
500 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
501 const gchar *who,
502 gchar *rights)
504 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
505 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
506 send_soap_request(sipe_private, body);
507 g_free(body);
510 static void
511 sipe_change_access_level(struct sipe_core_private *sipe_private,
512 const int container_id,
513 const gchar *type,
514 const gchar *value);
516 void
517 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
518 const gchar * who, gboolean allow)
520 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
522 if (allow) {
523 SIPE_DEBUG_INFO("Authorizing contact %s", who);
524 } else {
525 SIPE_DEBUG_INFO("Blocking contact %s", who);
528 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
529 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
530 } else {
531 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
535 static
536 void sipe_auth_user_cb(void * data)
538 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
539 if (!job) return;
541 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
542 g_free(job);
545 static
546 void sipe_deny_user_cb(void * data)
548 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
549 if (!job) return;
551 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
552 g_free(job);
555 /** @applicable: 2005-
557 static void
558 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
559 struct sipmsg * msg)
561 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
562 sipe_xml *watchers;
563 const sipe_xml *watcher;
564 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
565 if (msg->response != 0 && msg->response != 200) return;
567 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
569 watchers = sipe_xml_parse(msg->body, msg->bodylen);
570 if (!watchers) return;
572 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
573 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
574 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
575 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
577 // TODO pull out optional displayName to pass as alias
578 if (remote_user) {
579 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
580 job->who = remote_user;
581 job->sipe_private = sipe_private;
582 purple_account_request_authorization(
583 sip->account,
584 remote_user,
585 _("you"), /* id */
586 alias,
587 NULL, /* message */
588 on_list,
589 sipe_auth_user_cb,
590 sipe_deny_user_cb,
591 (void *) job);
596 sipe_xml_free(watchers);
597 return;
600 static void
601 sipe_group_add(struct sipe_core_private *sipe_private,
602 struct sipe_group * group)
604 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
605 PurpleGroup * purple_group = purple_find_group(group->name);
606 if (!purple_group) {
607 purple_group = purple_group_new(group->name);
608 purple_blist_add_group(purple_group, NULL);
611 if (purple_group) {
612 group->purple_group = purple_group;
613 sip->groups = g_slist_append(sip->groups, group);
614 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
615 } else {
616 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
620 static struct sipe_group *sipe_group_find_by_id(struct sipe_core_private *sipe_private,
621 int id)
623 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
624 struct sipe_group *group;
625 GSList *entry;
626 if (sip == NULL) {
627 return NULL;
630 entry = sip->groups;
631 while (entry) {
632 group = entry->data;
633 if (group->id == id) {
634 return group;
636 entry = entry->next;
638 return NULL;
641 static struct sipe_group *sipe_group_find_by_name(struct sipe_core_private *sipe_private,
642 const gchar * name)
644 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
645 struct sipe_group *group;
646 GSList *entry;
647 if (!sip || !name) {
648 return NULL;
651 entry = sip->groups;
652 while (entry) {
653 group = entry->data;
654 if (sipe_strequal(group->name, name)) {
655 return group;
657 entry = entry->next;
659 return NULL;
662 static void
663 sipe_group_rename(struct sipe_core_private *sipe_private,
664 struct sipe_group *group,
665 gchar *name)
667 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
668 gchar *body;
669 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
670 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
671 send_soap_request(sipe_private, body);
672 g_free(body);
673 g_free(group->name);
674 group->name = g_strdup(name);
678 * Only appends if no such value already stored.
679 * Like Set in Java.
681 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
682 GSList * res = list;
683 if (!g_slist_find_custom(list, data, func)) {
684 res = g_slist_insert_sorted(list, data, func);
686 return res;
689 static int
690 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
691 return group1->id - group2->id;
695 * Returns string like "2 4 7 8" - group ids buddy belong to.
697 static gchar *
698 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
699 int i = 0;
700 gchar *res;
701 //creating array from GList, converting int to gchar*
702 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
703 GSList *entry = buddy->groups;
705 if (!ids_arr) return NULL;
707 while (entry) {
708 struct sipe_group * group = entry->data;
709 ids_arr[i] = g_strdup_printf("%d", group->id);
710 entry = entry->next;
711 i++;
713 ids_arr[i] = NULL;
714 res = g_strjoinv(" ", ids_arr);
715 g_strfreev(ids_arr);
716 return res;
720 * Sends buddy update to server
722 void
723 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
725 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
726 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
727 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
729 if (buddy && purple_buddy) {
730 const char *alias = purple_buddy_get_alias(purple_buddy);
731 gchar *groups = sipe_get_buddy_groups_string(buddy);
732 if (groups) {
733 gchar *body;
734 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
736 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
737 alias, groups, "true", buddy->name, sip->contacts_delta++
739 send_soap_request(SIPE_CORE_PRIVATE, body);
740 g_free(groups);
741 g_free(body);
746 static gboolean process_add_group_response(struct sipe_core_private *sipe_private,
747 struct sipmsg *msg,
748 struct transaction *trans)
750 if (msg->response == 200) {
751 struct sipe_group *group;
752 struct group_user_context *ctx = trans->payload->data;
753 sipe_xml *xml;
754 const sipe_xml *node;
755 char *group_id;
756 struct sipe_buddy *buddy;
758 xml = sipe_xml_parse(msg->body, msg->bodylen);
759 if (!xml) {
760 return FALSE;
763 node = sipe_xml_child(xml, "Body/addGroup/groupID");
764 if (!node) {
765 sipe_xml_free(xml);
766 return FALSE;
769 group_id = sipe_xml_data(node);
770 if (!group_id) {
771 sipe_xml_free(xml);
772 return FALSE;
775 group = g_new0(struct sipe_group, 1);
776 group->id = (int)g_ascii_strtod(group_id, NULL);
777 g_free(group_id);
778 group->name = g_strdup(ctx->group_name);
780 sipe_group_add(sipe_private, group);
782 buddy = g_hash_table_lookup(sipe_private->buddies, ctx->user_name);
783 if (buddy) {
784 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
787 sipe_core_group_set_user(SIPE_CORE_PUBLIC, ctx->user_name);
789 sipe_xml_free(xml);
790 return TRUE;
792 return FALSE;
795 static void sipe_group_context_destroy(gpointer data)
797 struct group_user_context *ctx = data;
798 g_free(ctx->group_name);
799 g_free(ctx->user_name);
800 g_free(ctx);
803 static void sipe_group_create (struct sipe_core_private *sipe_private,
804 const gchar *name, const gchar * who)
806 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
807 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
808 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
809 gchar *body;
810 ctx->group_name = g_strdup(name);
811 ctx->user_name = g_strdup(who);
812 payload->destroy = sipe_group_context_destroy;
813 payload->data = ctx;
815 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
816 send_soap_request_with_cb(sipe_private, NULL, body, process_add_group_response, payload);
817 g_free(body);
820 static void
821 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
822 time_t calculate_from);
824 static int
825 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
827 static const char*
828 sipe_get_status_by_availability(int avail,
829 char** activity);
831 static void
832 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
833 const char *status_id,
834 const char *message,
835 time_t do_not_publish[]);
837 static void
838 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
839 struct sipe_buddy *sbuddy,
840 const char *status_id)
842 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
843 time_t cal_avail_since;
844 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
845 int avail;
846 gchar *self_uri;
848 if (!sbuddy) return;
850 if (cal_status < SIPE_CAL_NO_DATA) {
851 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
852 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
855 /* scheduled Cal update call */
856 if (!status_id) {
857 status_id = sbuddy->last_non_cal_status_id;
858 g_free(sbuddy->activity);
859 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
862 if (!status_id) {
863 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
864 sbuddy->name ? sbuddy->name : "" );
865 return;
868 /* adjust to calendar status */
869 if (cal_status != SIPE_CAL_NO_DATA) {
870 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
872 if (cal_status == SIPE_CAL_BUSY
873 && cal_avail_since > sbuddy->user_avail_since
874 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
876 status_id = SIPE_STATUS_ID_BUSY;
877 g_free(sbuddy->activity);
878 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
880 avail = sipe_get_availability_by_status(status_id, NULL);
882 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
883 if (cal_avail_since > sbuddy->activity_since) {
884 if (cal_status == SIPE_CAL_OOF
885 && avail >= 15000) /* 12000 in 2007 */
887 g_free(sbuddy->activity);
888 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
893 /* then set status_id actually */
894 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
895 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
897 /* set our account state to the one in roaming (including calendar info) */
898 self_uri = sip_uri_self(sipe_private);
899 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
900 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
901 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
904 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
905 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
907 g_free(self_uri);
910 static void
911 sipe_got_user_status(struct sipe_core_private *sipe_private,
912 const char* uri,
913 const char *status_id)
915 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
916 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
918 if (!sbuddy) return;
920 /* Check if on 2005 system contact's calendar,
921 * then set/preserve it.
923 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
924 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
925 } else {
926 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
930 static void
931 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
932 struct sipe_buddy *sbuddy,
933 struct sipe_core_private *sipe_private)
935 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
939 * Updates contact's status
940 * based on their calendar information.
942 * Applicability: 2005 systems
944 static void
945 update_calendar_status(struct sipe_core_private *sipe_private,
946 SIPE_UNUSED_PARAMETER void *unused)
948 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
949 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
951 /* repeat scheduling */
952 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
956 * Schedules process of contacts' status update
957 * based on their calendar information.
958 * Should be scheduled to the beginning of every
959 * 15 min interval, like:
960 * 13:00, 13:15, 13:30, 13:45, etc.
962 * Applicability: 2005 systems
964 static void
965 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
966 time_t calculate_from)
968 int interval = 15*60;
969 /** start of the beginning of closest 15 min interval. */
970 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
972 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
973 asctime(localtime(&calculate_from)));
974 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
975 asctime(localtime(&next_start)));
977 sipe_schedule_seconds(sipe_private,
978 "<+2005-cal-status>",
979 NULL,
980 next_start - time(NULL),
981 update_calendar_status,
982 NULL);
986 * Schedules process of self status publish
987 * based on own calendar information.
988 * Should be scheduled to the beginning of every
989 * 15 min interval, like:
990 * 13:00, 13:15, 13:30, 13:45, etc.
992 * Applicability: 2007+ systems
994 static void
995 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
996 time_t calculate_from)
998 int interval = 5*60;
999 /** start of the beginning of closest 5 min interval. */
1000 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
1002 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1003 asctime(localtime(&calculate_from)));
1004 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1005 asctime(localtime(&next_start)));
1007 sipe_schedule_seconds(sipe_private,
1008 "<+2007-cal-status>",
1009 NULL,
1010 next_start - time(NULL),
1011 publish_calendar_status_self,
1012 NULL);
1015 static void process_incoming_notify(struct sipe_core_private *sipe_private,
1016 struct sipmsg *msg,
1017 gboolean request,
1018 gboolean benotify);
1020 /** Should be g_free()'d
1022 static gchar *
1023 sipe_get_subscription_key(const gchar *event,
1024 const gchar *with)
1026 gchar *key = NULL;
1028 if (is_empty(event)) return NULL;
1030 if (event && sipe_strcase_equal(event, "presence")) {
1031 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1032 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1034 /* @TODO drop participated buddies' just_added flag */
1035 } else if (event) {
1036 /* Subscription is identified by <event> key */
1037 key = g_strdup_printf("<%s>", event);
1040 return key;
1043 gboolean process_subscribe_response(struct sipe_core_private *sipe_private,
1044 struct sipmsg *msg,
1045 SIPE_UNUSED_PARAMETER struct transaction *trans)
1047 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1048 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1049 const gchar *event = sipmsg_find_header(msg, "Event");
1050 gchar *key;
1052 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1053 if (!event) {
1054 struct sipmsg *request_msg = trans->msg;
1055 event = sipmsg_find_header(request_msg, "Event");
1058 key = sipe_get_subscription_key(event, with);
1060 /* 200 OK; 481 Call Leg Does Not Exist */
1061 if (key && (msg->response == 200 || msg->response == 481)) {
1062 if (g_hash_table_lookup(sip->subscriptions, key)) {
1063 g_hash_table_remove(sip->subscriptions, key);
1064 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
1068 /* create/store subscription dialog if not yet */
1069 if (msg->response == 200) {
1070 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1071 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1073 if (key) {
1074 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1075 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1077 subscription->dialog.callid = g_strdup(callid);
1078 subscription->dialog.cseq = atoi(cseq);
1079 subscription->dialog.with = g_strdup(with);
1080 subscription->event = g_strdup(event);
1081 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1083 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key);
1086 g_free(cseq);
1089 g_free(key);
1090 g_free(with);
1092 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1094 process_incoming_notify(sipe_private, msg, FALSE, FALSE);
1096 return TRUE;
1099 static void sipe_subscribe_resource_uri(const char *name,
1100 SIPE_UNUSED_PARAMETER gpointer value,
1101 gchar **resources_uri)
1103 gchar *tmp = *resources_uri;
1104 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1105 g_free(tmp);
1108 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1110 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1111 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1112 gchar *tmp = *resources_uri;
1114 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1116 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1117 g_free(tmp);
1121 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1122 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1123 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1124 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1125 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1128 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
1129 gchar *resources_uri,
1130 gchar *to)
1132 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1133 gchar *key;
1134 gchar *contact = get_contact(sipe_private);
1135 gchar *request;
1136 gchar *content;
1137 gchar *require = "";
1138 gchar *accept = "";
1139 gchar *autoextend = "";
1140 gchar *content_type;
1141 struct sip_dialog *dialog;
1143 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1144 require = ", categoryList";
1145 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1146 content_type = "application/msrtc-adrl-categorylist+xml";
1147 content = g_strdup_printf(
1148 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1149 "<action name=\"subscribe\" id=\"63792024\">\n"
1150 "<adhocList>\n%s</adhocList>\n"
1151 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1152 "<category name=\"calendarData\"/>\n"
1153 "<category name=\"contactCard\"/>\n"
1154 "<category name=\"note\"/>\n"
1155 "<category name=\"state\"/>\n"
1156 "</categoryList>\n"
1157 "</action>\n"
1158 "</batchSub>", sipe_private->username, resources_uri);
1159 } else {
1160 autoextend = "Supported: com.microsoft.autoextend\r\n";
1161 content_type = "application/adrl+xml";
1162 content = g_strdup_printf(
1163 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1164 "<create xmlns=\"\">\n%s</create>\n"
1165 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
1167 g_free(resources_uri);
1169 request = g_strdup_printf(
1170 "Require: adhoclist%s\r\n"
1171 "Supported: eventlist\r\n"
1172 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1173 "Supported: ms-piggyback-first-notify\r\n"
1174 "%sSupported: ms-benotify\r\n"
1175 "Proxy-Require: ms-benotify\r\n"
1176 "Event: presence\r\n"
1177 "Content-Type: %s\r\n"
1178 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1179 g_free(contact);
1181 /* subscribe to buddy presence */
1182 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1183 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1184 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1185 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
1187 send_sip_request(sipe_private, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1189 g_free(content);
1190 g_free(to);
1191 g_free(request);
1192 g_free(key);
1195 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
1196 SIPE_UNUSED_PARAMETER void *unused)
1198 gchar *to = sip_uri_self(sipe_private);
1199 gchar *resources_uri = g_strdup("");
1200 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1201 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1202 } else {
1203 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1206 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
1209 struct presence_batched_routed {
1210 gchar *host;
1211 GSList *buddies;
1214 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1216 struct presence_batched_routed *data = payload;
1217 GSList *buddies = data->buddies;
1218 while (buddies) {
1219 g_free(buddies->data);
1220 buddies = buddies->next;
1222 g_slist_free(data->buddies);
1223 g_free(data->host);
1224 g_free(payload);
1227 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
1228 void *payload)
1230 struct presence_batched_routed *data = payload;
1231 GSList *buddies = data->buddies;
1232 gchar *resources_uri = g_strdup("");
1233 while (buddies) {
1234 gchar *tmp = resources_uri;
1235 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1236 g_free(tmp);
1237 buddies = buddies->next;
1239 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
1240 g_strdup(data->host));
1244 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1245 * The user sends a single SUBSCRIBE request to the subscribed contact.
1246 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1250 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
1251 void *buddy_name)
1253 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1254 gchar *key;
1255 gchar *to = sip_uri((char *)buddy_name);
1256 gchar *tmp = get_contact(sipe_private);
1257 gchar *request;
1258 gchar *content = NULL;
1259 gchar *autoextend = "";
1260 gchar *content_type = "";
1261 struct sip_dialog *dialog;
1262 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
1263 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1265 if (sbuddy) sbuddy->just_added = FALSE;
1267 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1268 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1269 } else {
1270 autoextend = "Supported: com.microsoft.autoextend\r\n";
1273 request = g_strdup_printf(
1274 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1275 "Supported: ms-piggyback-first-notify\r\n"
1276 "%s%sSupported: ms-benotify\r\n"
1277 "Proxy-Require: ms-benotify\r\n"
1278 "Event: presence\r\n"
1279 "Contact: %s\r\n", autoextend, content_type, tmp);
1281 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1282 content = g_strdup_printf(
1283 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1284 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1285 "<resource uri=\"%s\"%s\n"
1286 "</adhocList>\n"
1287 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1288 "<category name=\"calendarData\"/>\n"
1289 "<category name=\"contactCard\"/>\n"
1290 "<category name=\"note\"/>\n"
1291 "<category name=\"state\"/>\n"
1292 "</categoryList>\n"
1293 "</action>\n"
1294 "</batchSub>", sipe_private->username, to, context);
1297 g_free(tmp);
1299 /* subscribe to buddy presence */
1300 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1301 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1302 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1303 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
1305 send_sip_request(sipe_private, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1307 g_free(content);
1308 g_free(to);
1309 g_free(request);
1310 g_free(key);
1313 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1315 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
1317 if (!purple_status_is_active(status))
1318 return;
1320 if (account->gc) {
1321 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
1322 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1324 if (sip) {
1325 gchar *action_name;
1326 gchar *tmp;
1327 time_t now = time(NULL);
1328 const char *status_id = purple_status_get_id(status);
1329 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
1330 sipe_activity activity = sipe_get_activity_by_token(status_id);
1331 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
1333 /* when other point of presence clears note, but we are keeping
1334 * state if OOF note.
1336 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
1337 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
1338 do_not_publish = FALSE;
1341 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
1342 status_id, (int)sip->do_not_publish[activity], (int)now);
1344 sip->do_not_publish[activity] = 0;
1345 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
1346 status_id, (int)sip->do_not_publish[activity]);
1348 if (do_not_publish)
1350 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
1351 return;
1354 g_free(sip->status);
1355 sip->status = g_strdup(status_id);
1357 /* hack to escape apostrof before comparison */
1358 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
1360 /* this will preserve OOF flag as well */
1361 if (!sipe_strequal(tmp, sip->note)) {
1362 sip->is_oof_note = FALSE;
1363 g_free(sip->note);
1364 sip->note = g_strdup(note);
1365 sip->note_since = time(NULL);
1367 g_free(tmp);
1369 /* schedule 2 sec to capture idle flag */
1370 action_name = g_strdup_printf("<%s>", "+set-status");
1371 sipe_schedule_seconds(sipe_private,
1372 action_name,
1373 NULL,
1374 SIPE_IDLE_SET_DELAY,
1375 send_presence_status,
1376 NULL);
1377 g_free(action_name);
1382 void
1383 sipe_set_idle(PurpleConnection * gc,
1384 int interval)
1386 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1388 if (gc) {
1389 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1390 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1392 if (sip) {
1393 sip->idle_switch = time(NULL);
1394 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1399 void
1400 sipe_group_buddy(PurpleConnection *gc,
1401 const char *who,
1402 const char *old_group_name,
1403 const char *new_group_name)
1405 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1406 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
1407 struct sipe_group * old_group = NULL;
1408 struct sipe_group * new_group;
1410 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1411 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1413 if(!buddy) { // buddy not in roaming list
1414 return;
1417 if (old_group_name) {
1418 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
1420 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
1422 if (old_group) {
1423 buddy->groups = g_slist_remove(buddy->groups, old_group);
1424 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1427 if (!new_group) {
1428 sipe_group_create(sipe_private, new_group_name, who);
1429 } else {
1430 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1431 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
1435 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1437 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1439 /* libpurple can call us with undefined buddy or group */
1440 if (buddy && group) {
1441 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1443 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1444 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1445 purple_blist_rename_buddy(buddy, buddy_name);
1446 g_free(buddy_name);
1448 /* Prepend sip: if needed */
1449 if (!g_str_has_prefix(buddy->name, "sip:")) {
1450 gchar *buf = sip_uri_from_name(buddy->name);
1451 purple_blist_rename_buddy(buddy, buf);
1452 g_free(buf);
1455 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
1456 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1457 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1458 b->name = g_strdup(buddy->name);
1459 b->just_added = TRUE;
1460 g_hash_table_insert(sipe_private->buddies, b->name, b);
1461 sipe_group_buddy(gc, b->name, NULL, group->name);
1462 /* @TODO should go to callback */
1463 sipe_subscribe_presence_single(sipe_private,
1464 b->name);
1465 } else {
1466 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
1471 static void sipe_free_buddy(struct sipe_buddy *buddy)
1473 #ifndef _WIN32
1475 * We are calling g_hash_table_foreach_steal(). That means that no
1476 * key/value deallocation functions are called. Therefore the glib
1477 * hash code does not touch the key (buddy->name) or value (buddy)
1478 * of the to-be-deleted hash node at all. It follows that we
1480 * - MUST free the memory for the key ourselves and
1481 * - ARE allowed to do it in this function
1483 * Conclusion: glib must be broken on the Windows platform if sipe
1484 * crashes with SIGTRAP when closing. You'll have to live
1485 * with the memory leak until this is fixed.
1487 g_free(buddy->name);
1488 #endif
1489 g_free(buddy->activity);
1490 g_free(buddy->meeting_subject);
1491 g_free(buddy->meeting_location);
1492 g_free(buddy->note);
1494 g_free(buddy->cal_start_time);
1495 g_free(buddy->cal_free_busy_base64);
1496 g_free(buddy->cal_free_busy);
1497 g_free(buddy->last_non_cal_activity);
1499 sipe_cal_free_working_hours(buddy->cal_working_hours);
1501 g_free(buddy->device_name);
1502 g_slist_free(buddy->groups);
1503 g_free(buddy);
1507 * Unassociates buddy from group first.
1508 * Then see if no groups left, removes buddy completely.
1509 * Otherwise updates buddy groups on server.
1511 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1513 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1514 struct sipe_buddy *b;
1515 struct sipe_group *g = NULL;
1517 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1518 if (!buddy) return;
1520 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
1521 if (!b) return;
1523 if (group) {
1524 g = sipe_group_find_by_name(sipe_private, group->name);
1527 if (g) {
1528 b->groups = g_slist_remove(b->groups, g);
1529 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
1532 if (g_slist_length(b->groups) < 1) {
1533 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1534 sipe_schedule_cancel(sipe_private, action_name);
1535 g_free(action_name);
1537 g_hash_table_remove(sipe_private->buddies, buddy->name);
1539 if (b->name) {
1540 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1541 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1542 send_soap_request(sipe_private, body);
1543 g_free(body);
1546 sipe_free_buddy(b);
1547 } else {
1548 //updates groups on server
1549 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1554 void
1555 sipe_rename_group(PurpleConnection *gc,
1556 const char *old_name,
1557 PurpleGroup *group,
1558 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1560 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1561 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, old_name);
1562 if (s_group) {
1563 sipe_group_rename(sipe_private, s_group, group->name);
1564 } else {
1565 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
1569 void
1570 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1572 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1573 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, group->name);
1574 if (s_group) {
1575 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1576 gchar *body;
1577 SIPE_DEBUG_INFO("Deleting group %s", group->name);
1578 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1579 send_soap_request(sipe_private, body);
1580 g_free(body);
1582 sip->groups = g_slist_remove(sip->groups, s_group);
1583 g_free(s_group->name);
1584 g_free(s_group);
1585 } else {
1586 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
1591 * A callback for g_hash_table_foreach
1593 static void
1594 sipe_buddy_subscribe_cb(char *buddy_name,
1595 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1596 struct sipe_core_private *sipe_private)
1598 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy_name);
1599 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1600 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1601 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1603 sipe_schedule_mseconds(sipe_private,
1604 action_name,
1605 g_strdup(buddy_name),
1606 timeout,
1607 sipe_subscribe_presence_single,
1608 g_free);
1609 g_free(action_name);
1613 * Removes entries from purple buddy list
1614 * that does not correspond ones in the roaming contact list.
1616 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1617 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1618 GSList *buddies = purple_find_buddies(sip->account, NULL);
1619 GSList *entry = buddies;
1620 struct sipe_buddy *buddy;
1621 PurpleBuddy *b;
1622 PurpleGroup *g;
1624 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
1625 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1626 while (entry) {
1627 b = entry->data;
1628 g = purple_buddy_get_group(b);
1629 buddy = g_hash_table_lookup(sipe_private->buddies, b->name);
1630 if(buddy) {
1631 gboolean in_sipe_groups = FALSE;
1632 GSList *entry2 = buddy->groups;
1633 while (entry2) {
1634 struct sipe_group *group = entry2->data;
1635 if (sipe_strequal(group->name, g->name)) {
1636 in_sipe_groups = TRUE;
1637 break;
1639 entry2 = entry2->next;
1641 if(!in_sipe_groups) {
1642 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
1643 purple_blist_remove_buddy(b);
1645 } else {
1646 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
1647 purple_blist_remove_buddy(b);
1649 entry = entry->next;
1651 g_slist_free(buddies);
1654 static int
1655 sipe_find_access_level(struct sipe_core_private *sipe_private,
1656 const gchar *type,
1657 const gchar *value,
1658 gboolean *is_group_access);
1660 static void
1661 sipe_refresh_blocked_status_cb(char *buddy_name,
1662 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1663 struct sipe_core_private *sipe_private)
1665 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1666 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1667 gboolean blocked = (container_id == 32000);
1668 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
1670 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1671 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1673 if (blocked != blocked_in_blist) {
1674 if (blocked) {
1675 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
1676 } else {
1677 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
1680 /* stupid workaround to make pidgin re-render screen to reflect our changes */
1682 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
1683 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
1684 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
1686 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
1687 sipe_got_user_status(sipe_private, buddy_name, purple_status_get_id(pstatus));
1693 static void
1694 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1696 g_hash_table_foreach(sipe_private->buddies,
1697 (GHFunc) sipe_refresh_blocked_status_cb,
1698 sipe_private);
1701 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1702 struct sipmsg *msg)
1704 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1705 int len = msg->bodylen;
1707 const gchar *tmp = sipmsg_find_header(msg, "Event");
1708 const sipe_xml *item;
1709 sipe_xml *isc;
1710 const gchar *contacts_delta;
1711 const sipe_xml *group_node;
1712 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1713 return FALSE;
1716 /* Convert the contact from XML to Purple Buddies */
1717 isc = sipe_xml_parse(msg->body, len);
1718 if (!isc) {
1719 return FALSE;
1722 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1723 if (contacts_delta) {
1724 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1727 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1729 /* Parse groups */
1730 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1731 struct sipe_group * group = g_new0(struct sipe_group, 1);
1732 const char *name = sipe_xml_attribute(group_node, "name");
1734 if (g_str_has_prefix(name, "~")) {
1735 name = _("Other Contacts");
1737 group->name = g_strdup(name);
1738 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1740 sipe_group_add(sipe_private, group);
1743 // Make sure we have at least one group
1744 if (g_slist_length(sip->groups) == 0) {
1745 struct sipe_group * group = g_new0(struct sipe_group, 1);
1746 PurpleGroup *purple_group;
1747 group->name = g_strdup(_("Other Contacts"));
1748 group->id = 1;
1749 purple_group = purple_group_new(group->name);
1750 purple_blist_add_group(purple_group, NULL);
1751 sip->groups = g_slist_append(sip->groups, group);
1754 /* Parse contacts */
1755 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1756 const gchar *uri = sipe_xml_attribute(item, "uri");
1757 const gchar *name = sipe_xml_attribute(item, "name");
1758 gchar *buddy_name;
1759 struct sipe_buddy *buddy = NULL;
1760 gchar *tmp;
1761 gchar **item_groups;
1762 int i = 0;
1764 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1765 tmp = sip_uri_from_name(uri);
1766 buddy_name = g_ascii_strdown(tmp, -1);
1767 g_free(tmp);
1769 /* assign to group Other Contacts if nothing else received */
1770 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1771 if(is_empty(tmp)) {
1772 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1773 g_free(tmp);
1774 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1776 item_groups = g_strsplit(tmp, " ", 0);
1777 g_free(tmp);
1779 while (item_groups[i]) {
1780 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1782 // If couldn't find the right group for this contact, just put them in the first group we have
1783 if (group == NULL && g_slist_length(sip->groups) > 0) {
1784 group = sip->groups->data;
1787 if (group != NULL) {
1788 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1789 if (!b){
1790 b = purple_buddy_new(sip->account, buddy_name, uri);
1791 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1793 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1796 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
1797 if (name != NULL && strlen(name) != 0) {
1798 purple_blist_alias_buddy(b, name);
1800 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1804 if (!buddy) {
1805 buddy = g_new0(struct sipe_buddy, 1);
1806 buddy->name = g_strdup(b->name);
1807 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1810 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1812 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
1813 } else {
1814 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1815 name);
1818 i++;
1819 } // while, contact groups
1820 g_strfreev(item_groups);
1821 g_free(buddy_name);
1823 } // for, contacts
1825 sipe_cleanup_local_blist(sipe_private);
1827 /* Add self-contact if not there yet. 2005 systems. */
1828 /* This will resemble subscription to roaming_self in 2007 systems */
1829 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1830 gchar *self_uri = sip_uri_self(sipe_private);
1831 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1833 if (!buddy) {
1834 buddy = g_new0(struct sipe_buddy, 1);
1835 buddy->name = g_strdup(self_uri);
1836 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1838 g_free(self_uri);
1841 sipe_xml_free(isc);
1843 /* subscribe to buddies */
1844 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1845 if (sip->batched_support) {
1846 sipe_subscribe_presence_batched(sipe_private, NULL);
1847 } else {
1848 g_hash_table_foreach(sipe_private->buddies,
1849 (GHFunc)sipe_buddy_subscribe_cb,
1850 sipe_private);
1852 sip->subscribed_buddies = TRUE;
1854 /* for 2005 systems schedule contacts' status update
1855 * based on their calendar information
1857 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1858 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1861 return 0;
1865 * Subscribe roaming contacts
1867 static void sipe_subscribe_roaming_contacts(struct sipe_core_private *sipe_private)
1869 gchar *to = sip_uri_self(sipe_private);
1870 gchar *tmp = get_contact(sipe_private);
1871 gchar *hdr = g_strdup_printf(
1872 "Event: vnd-microsoft-roaming-contacts\r\n"
1873 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
1874 "Supported: com.microsoft.autoextend\r\n"
1875 "Supported: ms-benotify\r\n"
1876 "Proxy-Require: ms-benotify\r\n"
1877 "Supported: ms-piggyback-first-notify\r\n"
1878 "Contact: %s\r\n", tmp);
1879 g_free(tmp);
1881 send_sip_request(sipe_private, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
1882 g_free(to);
1883 g_free(hdr);
1886 static void sipe_subscribe_presence_wpending(struct sipe_core_private *sipe_private,
1887 SIPE_UNUSED_PARAMETER void *unused)
1889 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1890 gchar *key;
1891 struct sip_dialog *dialog;
1892 gchar *to = sip_uri_self(sipe_private);
1893 gchar *tmp = get_contact(sipe_private);
1894 gchar *hdr = g_strdup_printf(
1895 "Event: presence.wpending\r\n"
1896 "Accept: text/xml+msrtc.wpending\r\n"
1897 "Supported: com.microsoft.autoextend\r\n"
1898 "Supported: ms-benotify\r\n"
1899 "Proxy-Require: ms-benotify\r\n"
1900 "Supported: ms-piggyback-first-notify\r\n"
1901 "Contact: %s\r\n", tmp);
1902 g_free(tmp);
1904 /* Subscription is identified by <event> key */
1905 key = g_strdup_printf("<%s>", "presence.wpending");
1906 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1907 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key, dialog ? "Not NULL" : "NULL");
1909 send_sip_request(sipe_private, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
1911 g_free(to);
1912 g_free(hdr);
1913 g_free(key);
1917 * Fires on deregistration event initiated by server.
1918 * [MS-SIPREGE] SIP extension.
1921 // 2007 Example
1923 // Content-Type: text/registration-event
1924 // subscription-state: terminated;expires=0
1925 // ms-diagnostics-public: 4141;reason="User disabled"
1927 // deregistered;event=rejected
1929 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1930 struct sipmsg *msg)
1932 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1933 gchar *event = NULL;
1934 gchar *reason = NULL;
1935 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
1936 gchar *warning;
1938 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
1939 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1941 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1942 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1943 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1944 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1945 } else {
1946 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1947 return;
1950 if (diagnostics != NULL) {
1951 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
1952 } else { // for LCS2005
1953 int error_id = 0;
1954 if (event && sipe_strcase_equal(event, "unregistered")) {
1955 error_id = 4140; // [MS-SIPREGE]
1956 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1957 reason = g_strdup(_("you are already signed in at another location"));
1958 } else if (event && sipe_strcase_equal(event, "rejected")) {
1959 error_id = 4141;
1960 reason = g_strdup(_("user disabled")); // [MS-OCER]
1961 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1962 error_id = 4142;
1963 reason = g_strdup(_("user moved")); // [MS-OCER]
1966 g_free(event);
1967 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1968 g_free(reason);
1970 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1971 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1972 warning);
1973 g_free(warning);
1977 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1978 struct sipmsg *msg)
1980 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1981 sipe_xml *xn_provision_group_list;
1982 const sipe_xml *node;
1984 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1986 /* provisionGroup */
1987 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1988 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1989 g_free(sip->focus_factory_uri);
1990 sip->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1991 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
1992 sip->focus_factory_uri ? sip->focus_factory_uri : "");
1993 break;
1996 sipe_xml_free(xn_provision_group_list);
1999 /** for 2005 system */
2000 static void
2001 sipe_process_provisioning(struct sipe_core_private *sipe_private,
2002 struct sipmsg *msg)
2004 sipe_xml *xn_provision;
2005 const sipe_xml *node;
2007 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
2008 if ((node = sipe_xml_child(xn_provision, "user"))) {
2009 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
2010 if ((node = sipe_xml_child(node, "line"))) {
2011 const gchar *line_uri = sipe_xml_attribute(node, "uri");
2012 const gchar *server = sipe_xml_attribute(node, "server");
2013 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
2014 sip_csta_open(sipe_private, line_uri, server);
2017 sipe_xml_free(xn_provision);
2020 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
2021 struct sipmsg *msg)
2023 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2024 const gchar *contacts_delta;
2025 sipe_xml *xml;
2027 xml = sipe_xml_parse(msg->body, msg->bodylen);
2028 if (!xml)
2030 return;
2033 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
2034 if (contacts_delta)
2036 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2039 sipe_xml_free(xml);
2042 static void
2043 free_container_member(struct sipe_container_member *member)
2045 if (!member) return;
2047 g_free(member->type);
2048 g_free(member->value);
2049 g_free(member);
2052 static void
2053 free_container(struct sipe_container *container)
2055 GSList *entry;
2057 if (!container) return;
2059 entry = container->members;
2060 while (entry) {
2061 void *data = entry->data;
2062 entry = g_slist_remove(entry, data);
2063 free_container_member((struct sipe_container_member *)data);
2065 g_free(container);
2068 static void
2069 sipe_send_container_members_prepare(const guint container_id,
2070 const guint container_version,
2071 const gchar *action,
2072 const gchar *type,
2073 const gchar *value,
2074 char **container_xmls)
2076 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2077 gchar *body;
2079 if (!container_xmls) return;
2081 body = g_strdup_printf(
2082 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2083 container_id,
2084 container_version,
2085 action,
2086 type,
2087 value_str);
2088 g_free(value_str);
2090 if ((*container_xmls) == NULL) {
2091 *container_xmls = body;
2092 } else {
2093 char *tmp = *container_xmls;
2095 *container_xmls = g_strconcat(*container_xmls, body, NULL);
2096 g_free(tmp);
2097 g_free(body);
2101 static void
2102 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
2103 char *container_xmls)
2105 gchar *self;
2106 gchar *contact;
2107 gchar *hdr;
2108 gchar *body;
2110 if (!container_xmls) return;
2112 self = sip_uri_self(sipe_private);
2113 body = g_strdup_printf(
2114 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2115 "%s"
2116 "</setContainerMembers>",
2117 container_xmls);
2119 contact = get_contact(sipe_private);
2120 hdr = g_strdup_printf("Contact: %s\r\n"
2121 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2122 g_free(contact);
2124 send_sip_request(sipe_private, "SERVICE", self, self, hdr, body, NULL, NULL);
2126 g_free(hdr);
2127 g_free(body);
2128 g_free(self);
2132 * Finds locally stored MS-PRES container member
2134 static struct sipe_container_member *
2135 sipe_find_container_member(struct sipe_container *container,
2136 const gchar *type,
2137 const gchar *value)
2139 struct sipe_container_member *member;
2140 GSList *entry;
2142 if (container == NULL || type == NULL) {
2143 return NULL;
2146 entry = container->members;
2147 while (entry) {
2148 member = entry->data;
2149 if (sipe_strcase_equal(member->type, type) &&
2150 sipe_strcase_equal(member->value, value))
2152 return member;
2154 entry = entry->next;
2156 return NULL;
2160 * Finds locally stored MS-PRES container by id
2162 static struct sipe_container *
2163 sipe_find_container(struct sipe_core_private *sipe_private,
2164 guint id)
2166 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2167 struct sipe_container *container;
2168 GSList *entry;
2170 if (sip == NULL) {
2171 return NULL;
2174 entry = sip->containers;
2175 while (entry) {
2176 container = entry->data;
2177 if (id == container->id) {
2178 return container;
2180 entry = entry->next;
2182 return NULL;
2185 static GSList *
2186 sipe_get_access_domains(struct sipe_core_private *sipe_private)
2188 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2189 struct sipe_container *container;
2190 struct sipe_container_member *member;
2191 GSList *entry;
2192 GSList *entry2;
2193 GSList *res = NULL;
2195 if (!sip) return NULL;
2197 entry = sip->containers;
2198 while (entry) {
2199 container = entry->data;
2201 entry2 = container->members;
2202 while (entry2) {
2203 member = entry2->data;
2204 if (sipe_strcase_equal(member->type, "domain"))
2206 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
2208 entry2 = entry2->next;
2210 entry = entry->next;
2212 return res;
2216 * Returns pointer to domain part in provided Email URL
2218 * @param email an email URL. Example: first.last@hq.company.com
2219 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
2221 * Doesn't allocate memory
2223 static const char *
2224 sipe_get_domain(const char *email)
2226 char *tmp;
2228 if (!email) return NULL;
2230 tmp = strstr(email, "@");
2232 if (tmp && ((tmp+1) < (email + strlen(email)))) {
2233 return tmp+1;
2234 } else {
2235 return NULL;
2240 /* @TODO: replace with binary search for faster access? */
2241 /** source: http://support.microsoft.com/kb/897567 */
2242 static const char * const public_domains [] = {
2243 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
2244 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
2245 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
2246 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
2247 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
2248 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
2249 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
2250 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
2251 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
2252 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
2253 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
2254 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
2255 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
2256 "yahoo.com",
2257 NULL};
2259 static gboolean
2260 sipe_is_public_domain(const char *domain)
2262 int i = 0;
2263 while (public_domains[i]) {
2264 if (sipe_strcase_equal(public_domains[i], domain)) {
2265 return TRUE;
2267 i++;
2269 return FALSE;
2273 * Access Levels
2274 * 32000 - Blocked
2275 * 400 - Personal
2276 * 300 - Team
2277 * 200 - Company
2278 * 100 - Public
2280 static const char *
2281 sipe_get_access_level_name(int container_id)
2283 switch(container_id) {
2284 case 32000: return _("Blocked");
2285 case 400: return _("Personal");
2286 case 300: return _("Team");
2287 case 200: return _("Company");
2288 case 100: return _("Public");
2290 return _("Unknown");
2293 static const guint containers[] = {32000, 400, 300, 200, 100};
2294 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
2297 static int
2298 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
2299 const gchar *type,
2300 const gchar *value)
2302 unsigned int i = 0;
2303 const gchar *value_mod = value;
2305 if (!type) return -1;
2307 if (sipe_strequal("user", type)) {
2308 value_mod = sipe_get_no_sip_uri(value);
2311 for (i = 0; i < CONTAINERS_LEN; i++) {
2312 struct sipe_container_member *member;
2313 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
2314 if (!container) continue;
2316 member = sipe_find_container_member(container, type, value_mod);
2317 if (member) return containers[i];
2320 return -1;
2323 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
2324 static int
2325 sipe_find_access_level(struct sipe_core_private *sipe_private,
2326 const gchar *type,
2327 const gchar *value,
2328 gboolean *is_group_access)
2330 int container_id = -1;
2332 if (sipe_strequal("user", type)) {
2333 const char *domain;
2334 const char *no_sip_uri = sipe_get_no_sip_uri(value);
2336 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
2337 if (container_id >= 0) {
2338 if (is_group_access) *is_group_access = FALSE;
2339 return container_id;
2342 domain = sipe_get_domain(no_sip_uri);
2343 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
2344 if (container_id >= 0) {
2345 if (is_group_access) *is_group_access = TRUE;
2346 return container_id;
2349 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
2350 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
2351 if (is_group_access) *is_group_access = TRUE;
2352 return container_id;
2355 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
2356 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
2357 if (is_group_access) *is_group_access = TRUE;
2358 return container_id;
2361 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
2362 if ((container_id >= 0)) {
2363 if (is_group_access) *is_group_access = TRUE;
2364 return container_id;
2366 } else {
2367 container_id = sipe_find_member_access_level(sipe_private, type, value);
2368 if (is_group_access) *is_group_access = FALSE;
2371 return container_id;
2375 * @param container_id a new access level. If -1 then current access level
2376 * is just removed (I.e. the member is removed from all containers).
2377 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
2378 * @param value a value for member. E.g. SIP URI for "user" member type.
2380 static void
2381 sipe_change_access_level(struct sipe_core_private *sipe_private,
2382 const int container_id,
2383 const gchar *type,
2384 const gchar *value)
2386 unsigned int i;
2387 int current_container_id = -1;
2388 char *container_xmls = NULL;
2390 /* for each container: find/delete */
2391 for (i = 0; i < CONTAINERS_LEN; i++) {
2392 struct sipe_container_member *member;
2393 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
2395 if (!container) continue;
2397 member = sipe_find_container_member(container, type, value);
2398 if (member) {
2399 current_container_id = containers[i];
2400 /* delete/publish current access level */
2401 if (container_id < 0 || container_id != current_container_id) {
2402 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
2403 /* remove member from our cache, to be able to recalculate AL below */
2404 container->members = g_slist_remove(container->members, member);
2405 current_container_id = -1;
2410 /* recalculate AL below */
2411 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
2413 /* assign/publish new access level */
2414 if (container_id != current_container_id && container_id >= 0) {
2415 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
2416 guint version = container ? container->version : 0;
2418 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
2421 if (container_xmls) {
2422 sipe_send_set_container_members(sipe_private, container_xmls);
2424 g_free(container_xmls);
2427 static void
2428 free_publication(struct sipe_publication *publication)
2430 g_free(publication->category);
2431 g_free(publication->cal_event_hash);
2432 g_free(publication->note);
2434 g_free(publication->working_hours_xml_str);
2435 g_free(publication->fb_start_str);
2436 g_free(publication->free_busy_base64);
2438 g_free(publication);
2441 /* key is <category><instance><container> */
2442 static gboolean
2443 sipe_is_our_publication(struct sipe_core_private *sipe_private,
2444 const gchar *key)
2446 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2447 GSList *entry;
2449 /* filling keys for our publications if not yet cached */
2450 if (!sip->our_publication_keys) {
2451 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
2452 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
2453 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
2454 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
2455 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
2456 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
2457 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
2459 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2460 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2461 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2462 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2463 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2464 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2465 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2466 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2467 SIPE_DEBUG_INFO("\tNote : %u", 0);
2468 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2470 /* device */
2471 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2472 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2474 /* state:machineState */
2475 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2476 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2477 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2478 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2480 /* state:userState */
2481 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2482 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2483 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2484 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2486 /* state:calendarState */
2487 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2488 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2489 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2490 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2492 /* state:calendarState OOF */
2493 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2494 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2495 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2496 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2498 /* note */
2499 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2500 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2501 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2502 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2503 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2504 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2506 /* note OOF */
2507 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2508 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2509 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2510 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2511 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2512 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2514 /* calendarData:WorkingHours */
2515 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2516 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2517 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2518 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2519 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2520 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2521 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2522 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2523 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2524 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2525 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2526 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2528 /* calendarData:FreeBusy */
2529 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2530 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2531 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2532 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2533 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2534 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2535 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2536 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2537 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2538 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2539 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2540 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2542 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
2543 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2546 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
2548 entry = sip->our_publication_keys;
2549 while (entry) {
2550 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
2551 if (sipe_strequal(entry->data, key)) {
2552 return TRUE;
2554 entry = entry->next;
2556 return FALSE;
2559 /** Property names to store in blist.xml */
2560 #define ALIAS_PROP "alias"
2561 #define EMAIL_PROP "email"
2562 #define PHONE_PROP "phone"
2563 #define PHONE_DISPLAY_PROP "phone-display"
2564 #define PHONE_MOBILE_PROP "phone-mobile"
2565 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2566 #define PHONE_HOME_PROP "phone-home"
2567 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2568 #define PHONE_OTHER_PROP "phone-other"
2569 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2570 #define PHONE_CUSTOM1_PROP "phone-custom1"
2571 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2572 #define SITE_PROP "site"
2573 #define COMPANY_PROP "company"
2574 #define DEPARTMENT_PROP "department"
2575 #define TITLE_PROP "title"
2576 #define OFFICE_PROP "office"
2577 /** implies work address */
2578 #define ADDRESS_STREET_PROP "address-street"
2579 #define ADDRESS_CITY_PROP "address-city"
2580 #define ADDRESS_STATE_PROP "address-state"
2581 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2582 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2585 * Tries to figure out user first and last name
2586 * based on Display Name and email properties.
2588 * Allocates memory - must be g_free()'d
2590 * Examples to parse:
2591 * First Last
2592 * First Last - Company Name
2593 * Last, First
2594 * Last, First M.
2595 * Last, First (C)(STP) (Company)
2596 * first.last@company.com (preprocessed as "first last")
2597 * first.last.company.com@reuters.net (preprocessed as "first last company com")
2599 * Unusable examples:
2600 * user@company.com (preprocessed as "user")
2601 * first.m.last@company.com (preprocessed as "first m last")
2602 * user.company.com@reuters.net (preprocessed as "user company com")
2604 static void
2605 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
2606 const char *uri,
2607 char **first_name,
2608 char **last_name)
2610 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2611 PurpleBuddy *p_buddy;
2612 char *display_name;
2613 const char *email;
2614 const char *first, *last;
2615 char *tmp;
2616 char **parts;
2617 gboolean has_comma = FALSE;
2619 if (!sip || !uri) return;
2621 p_buddy = purple_find_buddy(sip->account, uri);
2623 if (!p_buddy) return;
2625 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
2626 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
2628 if (!display_name && !email) return;
2630 /* if no display name, make "first last anything_else" out of email */
2631 if (email && !display_name) {
2632 display_name = g_strndup(email, strstr(email, "@") - email);
2633 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2634 g_free(tmp);
2637 if (display_name) {
2638 has_comma = (strstr(display_name, ",") != NULL);
2639 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2640 g_free(tmp);
2641 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2642 g_free(tmp);
2645 parts = g_strsplit(display_name, " ", 0);
2647 if (!parts[0] || !parts[1]) {
2648 g_free(display_name);
2649 g_strfreev(parts);
2650 return;
2653 if (has_comma) {
2654 last = parts[0];
2655 first = parts[1];
2656 } else {
2657 first = parts[0];
2658 last = parts[1];
2661 if (first_name) {
2662 *first_name = g_strstrip(g_strdup(first));
2665 if (last_name) {
2666 *last_name = g_strstrip(g_strdup(last));
2669 g_free(display_name);
2670 g_strfreev(parts);
2674 * Update user information
2676 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2677 * @param property_name
2678 * @param property_value may be modified to strip white space
2680 static void
2681 sipe_update_user_info(struct sipe_core_private *sipe_private,
2682 const char *uri,
2683 const char *property_name,
2684 char *property_value)
2686 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2687 GSList *buddies, *entry;
2689 if (!property_name || strlen(property_name) == 0) return;
2691 if (property_value)
2692 property_value = g_strstrip(property_value);
2694 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
2695 while (entry) {
2696 const char *prop_str;
2697 const char *server_alias;
2698 PurpleBuddy *p_buddy = entry->data;
2700 /* for Display Name */
2701 if (sipe_strequal(property_name, ALIAS_PROP)) {
2702 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
2703 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2704 purple_blist_alias_buddy(p_buddy, property_value);
2707 server_alias = purple_buddy_get_server_alias(p_buddy);
2708 if (!is_empty(property_value) &&
2709 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2711 purple_blist_server_alias_buddy(p_buddy, property_value);
2714 /* for other properties */
2715 else {
2716 if (!is_empty(property_value)) {
2717 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
2718 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2719 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
2724 entry = entry->next;
2726 g_slist_free(buddies);
2730 * Update user phone
2731 * Suitable for both 2005 and 2007 systems.
2733 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2734 * @param phone_type
2735 * @param phone may be modified to strip white space
2736 * @param phone_display_string may be modified to strip white space
2738 static void
2739 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2740 const char *uri,
2741 const gchar *phone_type,
2742 gchar *phone,
2743 gchar *phone_display_string)
2745 const char *phone_node = PHONE_PROP; /* work phone by default */
2746 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
2748 if(!phone || strlen(phone) == 0) return;
2750 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2751 phone_node = PHONE_MOBILE_PROP;
2752 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
2753 } else if (sipe_strequal(phone_type, "home")) {
2754 phone_node = PHONE_HOME_PROP;
2755 phone_display_node = PHONE_HOME_DISPLAY_PROP;
2756 } else if (sipe_strequal(phone_type, "other")) {
2757 phone_node = PHONE_OTHER_PROP;
2758 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
2759 } else if (sipe_strequal(phone_type, "custom1")) {
2760 phone_node = PHONE_CUSTOM1_PROP;
2761 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
2764 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2765 if (phone_display_string) {
2766 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2770 void
2771 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2773 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2775 /* Do in parallel.
2776 * If failed, the branch will be disabled for subsequent calls.
2777 * Can't rely that user turned the functionality on in account settings.
2779 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2780 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2782 /* schedule repeat */
2783 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2784 "<+update-calendar>",
2785 NULL,
2786 UPDATE_CALENDAR_INTERVAL,
2787 (sipe_schedule_action)sipe_core_update_calendar,
2788 NULL);
2790 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2794 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2795 * by using standard Purple's means of signals and saved statuses.
2797 * Thus all UI elements get updated: Status Button with Note, docklet.
2798 * This is ablolutely important as both our status and note can come
2799 * inbound (roaming) or be updated programmatically (e.g. based on our
2800 * calendar data).
2802 static void
2803 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2804 const char *status_id,
2805 const char *message,
2806 time_t do_not_publish[])
2808 PurpleStatus *status = purple_account_get_active_status(account);
2809 gboolean changed = TRUE;
2811 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2812 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2814 changed = FALSE;
2817 if (purple_savedstatus_is_idleaway()) {
2818 changed = FALSE;
2821 if (changed) {
2822 PurpleSavedStatus *saved_status;
2823 const PurpleStatusType *acct_status_type =
2824 purple_status_type_find_with_id(account->status_types, status_id);
2825 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2826 sipe_activity activity = sipe_get_activity_by_token(status_id);
2828 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2829 if (saved_status) {
2830 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2833 /* If this type+message is unique then create a new transient saved status
2834 * Ref: gtkstatusbox.c
2836 if (!saved_status) {
2837 GList *tmp;
2838 GList *active_accts = purple_accounts_get_all_active();
2840 saved_status = purple_savedstatus_new(NULL, primitive);
2841 purple_savedstatus_set_message(saved_status, message);
2843 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2844 purple_savedstatus_set_substatus(saved_status,
2845 (PurpleAccount *)tmp->data, acct_status_type, message);
2847 g_list_free(active_accts);
2850 do_not_publish[activity] = time(NULL);
2851 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2852 status_id, (int)do_not_publish[activity]);
2854 /* Set the status for each account */
2855 purple_savedstatus_activate(saved_status);
2859 struct hash_table_delete_payload {
2860 GHashTable *hash_table;
2861 guint container;
2864 static void
2865 sipe_remove_category_container_publications_cb(const char *name,
2866 struct sipe_publication *publication,
2867 struct hash_table_delete_payload *payload)
2869 if (publication->container == payload->container) {
2870 g_hash_table_remove(payload->hash_table, name);
2873 static void
2874 sipe_remove_category_container_publications(GHashTable *our_publications,
2875 const char *category,
2876 guint container)
2878 struct hash_table_delete_payload payload;
2879 payload.hash_table = g_hash_table_lookup(our_publications, category);
2881 if (!payload.hash_table) return;
2883 payload.container = container;
2884 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2887 static void
2888 send_publish_category_initial(struct sipe_core_private *sipe_private);
2891 * When we receive some self (BE) NOTIFY with a new subscriber
2892 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2895 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2896 struct sipmsg *msg)
2898 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2899 gchar *contact;
2900 gchar *to;
2901 sipe_xml *xml;
2902 const sipe_xml *node;
2903 const sipe_xml *node2;
2904 char *display_name = NULL;
2905 char *uri;
2906 GSList *category_names = NULL;
2907 int aggreg_avail = 0;
2908 gboolean do_update_status = FALSE;
2909 gboolean has_note_cleaned = FALSE;
2911 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2913 xml = sipe_xml_parse(msg->body, msg->bodylen);
2914 if (!xml) return;
2916 contact = get_contact(sipe_private);
2917 to = sip_uri_self(sipe_private);
2920 /* categories */
2921 /* set list of categories participating in this XML */
2922 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2923 const gchar *name = sipe_xml_attribute(node, "name");
2924 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2926 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2927 category_names ? (int) g_slist_length(category_names) : -1);
2928 /* drop category information */
2929 if (category_names) {
2930 GSList *entry = category_names;
2931 while (entry) {
2932 GHashTable *cat_publications;
2933 const gchar *category = entry->data;
2934 entry = entry->next;
2935 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2936 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2937 if (cat_publications) {
2938 g_hash_table_remove(sip->our_publications, category);
2939 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2943 g_slist_free(category_names);
2944 /* filling our categories reflected in roaming data */
2945 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2946 const char *tmp;
2947 const gchar *name = sipe_xml_attribute(node, "name");
2948 guint container = sipe_xml_int_attribute(node, "container", -1);
2949 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2950 guint version = sipe_xml_int_attribute(node, "version", 0);
2951 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2952 sipe_utils_str_to_time(tmp) : 0;
2953 gchar *key;
2954 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2956 /* Ex. clear note: <category name="note"/> */
2957 if (container == (guint)-1) {
2958 g_free(sip->note);
2959 sip->note = NULL;
2960 do_update_status = TRUE;
2961 continue;
2964 /* Ex. clear note: <category name="note" container="200"/> */
2965 if (instance == (guint)-1) {
2966 if (container == 200) {
2967 g_free(sip->note);
2968 sip->note = NULL;
2969 do_update_status = TRUE;
2971 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2972 sipe_remove_category_container_publications(
2973 sip->our_publications, name, container);
2974 continue;
2977 /* key is <category><instance><container> */
2978 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2979 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2981 /* capture all userState publication for later clean up if required */
2982 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2983 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2985 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2986 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2987 publication->category = g_strdup(name);
2988 publication->instance = instance;
2989 publication->container = container;
2990 publication->version = version;
2992 if (!sip->user_state_publications) {
2993 sip->user_state_publications = g_hash_table_new_full(
2994 g_str_hash, g_str_equal,
2995 g_free, (GDestroyNotify)free_publication);
2997 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2998 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2999 key, version);
3003 if (sipe_is_our_publication(sipe_private, key)) {
3004 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3006 publication->category = g_strdup(name);
3007 publication->instance = instance;
3008 publication->container = container;
3009 publication->version = version;
3011 /* filling publication->availability */
3012 if (sipe_strequal(name, "state")) {
3013 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3014 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3016 if (xn_avail) {
3017 gchar *avail_str = sipe_xml_data(xn_avail);
3018 if (avail_str) {
3019 publication->availability = atoi(avail_str);
3021 g_free(avail_str);
3023 /* for calendarState */
3024 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
3025 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
3026 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
3028 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
3029 if (xn_activity) {
3030 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
3031 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
3033 event->is_meeting = TRUE;
3036 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
3037 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
3039 publication->cal_event_hash = sipe_cal_event_hash(event);
3040 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3041 publication->cal_event_hash);
3042 sipe_cal_event_free(event);
3045 /* filling publication->note */
3046 if (sipe_strequal(name, "note")) {
3047 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
3049 if (!has_note_cleaned) {
3050 has_note_cleaned = TRUE;
3052 g_free(sip->note);
3053 sip->note = NULL;
3054 sip->note_since = publish_time;
3056 do_update_status = TRUE;
3059 g_free(publication->note);
3060 publication->note = NULL;
3061 if (xn_body) {
3062 char *tmp;
3064 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
3065 g_free(tmp);
3066 if (publish_time >= sip->note_since) {
3067 g_free(sip->note);
3068 sip->note = g_strdup(publication->note);
3069 sip->note_since = publish_time;
3070 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
3072 do_update_status = TRUE;
3077 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3078 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
3079 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
3080 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
3081 if (xn_free_busy) {
3082 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3083 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
3085 if (xn_working_hours) {
3086 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
3090 if (!cat_publications) {
3091 cat_publications = g_hash_table_new_full(
3092 g_str_hash, g_str_equal,
3093 g_free, (GDestroyNotify)free_publication);
3094 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
3095 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
3097 g_hash_table_insert(cat_publications, g_strdup(key), publication);
3098 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
3100 g_free(key);
3102 /* aggregateState (not an our publication) from 2-nd container */
3103 if (sipe_strequal(name, "state") && container == 2) {
3104 const sipe_xml *xn_state = sipe_xml_child(node, "state");
3106 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
3107 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
3109 if (xn_avail) {
3110 gchar *avail_str = sipe_xml_data(xn_avail);
3111 if (avail_str) {
3112 aggreg_avail = atoi(avail_str);
3114 g_free(avail_str);
3117 do_update_status = TRUE;
3121 /* userProperties published by server from AD */
3122 if (!sip->csta && sipe_strequal(name, "userProperties")) {
3123 const sipe_xml *line;
3124 /* line, for Remote Call Control (RCC) */
3125 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
3126 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
3127 const gchar *line_type = sipe_xml_attribute(line, "lineType");
3128 gchar *line_uri;
3130 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
3132 line_uri = sipe_xml_data(line);
3133 if (line_uri) {
3134 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
3135 sip_csta_open(sipe_private, line_uri, line_server);
3137 g_free(line_uri);
3139 break;
3143 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
3144 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
3146 /* containers */
3147 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
3148 guint id = sipe_xml_int_attribute(node, "id", 0);
3149 struct sipe_container *container = sipe_find_container(sipe_private, id);
3151 if (container) {
3152 sip->containers = g_slist_remove(sip->containers, container);
3153 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
3154 free_container(container);
3156 container = g_new0(struct sipe_container, 1);
3157 container->id = id;
3158 container->version = sipe_xml_int_attribute(node, "version", 0);
3159 sip->containers = g_slist_append(sip->containers, container);
3160 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
3162 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
3163 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
3164 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
3165 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
3166 container->members = g_slist_append(container->members, member);
3167 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
3168 member->type, member->value ? member->value : "");
3172 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
3173 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
3174 char *container_xmls = NULL;
3175 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
3176 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
3178 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
3179 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
3180 /* initial set-up to let counterparties see your status */
3181 if (sameEnterpriseAL < 0) {
3182 struct sipe_container *container = sipe_find_container(sipe_private, 200);
3183 guint version = container ? container->version : 0;
3184 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
3186 if (federatedAL < 0) {
3187 struct sipe_container *container = sipe_find_container(sipe_private, 100);
3188 guint version = container ? container->version : 0;
3189 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
3191 sip->access_level_set = TRUE;
3193 if (container_xmls) {
3194 sipe_send_set_container_members(sipe_private, container_xmls);
3196 g_free(container_xmls);
3199 /* Refresh contacts' blocked status */
3200 sipe_refresh_blocked_status(sipe_private);
3202 /* subscribers */
3203 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
3204 const char *user;
3205 const char *acknowledged;
3206 gchar *hdr;
3207 gchar *body;
3209 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
3210 if (!user) continue;
3211 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
3212 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
3213 uri = sip_uri_from_name(user);
3215 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
3217 acknowledged= sipe_xml_attribute(node, "acknowledged");
3218 if(sipe_strcase_equal(acknowledged,"false")){
3219 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
3220 if (!purple_find_buddy(sip->account, uri)) {
3221 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
3224 hdr = g_strdup_printf(
3225 "Contact: %s\r\n"
3226 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
3228 body = g_strdup_printf(
3229 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3230 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3231 "</setSubscribers>", user);
3233 send_sip_request(sipe_private, "SERVICE", to, to, hdr, body, NULL, NULL);
3234 g_free(body);
3235 g_free(hdr);
3237 g_free(display_name);
3238 g_free(uri);
3241 g_free(contact);
3242 sipe_xml_free(xml);
3244 /* Publish initial state if not yet.
3245 * Assuming this happens on initial responce to subscription to roaming-self
3246 * so we've already updated our roaming data in full.
3247 * Only for 2007+
3249 if (!sip->initial_state_published) {
3250 send_publish_category_initial(sipe_private);
3251 sip->initial_state_published = TRUE;
3252 /* dalayed run */
3253 sipe_schedule_seconds(sipe_private,
3254 "<+update-calendar>",
3255 NULL,
3256 UPDATE_CALENDAR_DELAY,
3257 (sipe_schedule_action)sipe_core_update_calendar,
3258 NULL);
3259 do_update_status = FALSE;
3260 } else if (aggreg_avail) {
3262 g_free(sip->status);
3263 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
3264 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
3265 } else {
3266 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
3270 if (do_update_status) {
3271 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
3272 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
3275 g_free(to);
3278 static void sipe_subscribe_roaming_acl(struct sipe_core_private *sipe_private)
3280 gchar *to = sip_uri_self(sipe_private);
3281 gchar *tmp = get_contact(sipe_private);
3282 gchar *hdr = g_strdup_printf(
3283 "Event: vnd-microsoft-roaming-ACL\r\n"
3284 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3285 "Supported: com.microsoft.autoextend\r\n"
3286 "Supported: ms-benotify\r\n"
3287 "Proxy-Require: ms-benotify\r\n"
3288 "Supported: ms-piggyback-first-notify\r\n"
3289 "Contact: %s\r\n", tmp);
3290 g_free(tmp);
3292 send_sip_request(sipe_private, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
3293 g_free(to);
3294 g_free(hdr);
3298 * To request for presence information about the user, access level settings that have already been configured by the user
3299 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3300 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3303 static void sipe_subscribe_roaming_self(struct sipe_core_private *sipe_private)
3305 gchar *to = sip_uri_self(sipe_private);
3306 gchar *tmp = get_contact(sipe_private);
3307 gchar *hdr = g_strdup_printf(
3308 "Event: vnd-microsoft-roaming-self\r\n"
3309 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3310 "Supported: ms-benotify\r\n"
3311 "Proxy-Require: ms-benotify\r\n"
3312 "Supported: ms-piggyback-first-notify\r\n"
3313 "Contact: %s\r\n"
3314 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
3316 gchar *body=g_strdup(
3317 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3318 "<roaming type=\"categories\"/>"
3319 "<roaming type=\"containers\"/>"
3320 "<roaming type=\"subscribers\"/></roamingList>");
3322 g_free(tmp);
3323 send_sip_request(sipe_private, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3324 g_free(body);
3325 g_free(to);
3326 g_free(hdr);
3330 * For 2005 version
3332 static void sipe_subscribe_roaming_provisioning(struct sipe_core_private *sipe_private)
3334 gchar *to = sip_uri_self(sipe_private);
3335 gchar *tmp = get_contact(sipe_private);
3336 gchar *hdr = g_strdup_printf(
3337 "Event: vnd-microsoft-provisioning\r\n"
3338 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3339 "Supported: com.microsoft.autoextend\r\n"
3340 "Supported: ms-benotify\r\n"
3341 "Proxy-Require: ms-benotify\r\n"
3342 "Supported: ms-piggyback-first-notify\r\n"
3343 "Expires: 0\r\n"
3344 "Contact: %s\r\n", tmp);
3346 g_free(tmp);
3347 send_sip_request(sipe_private, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3348 g_free(to);
3349 g_free(hdr);
3352 /** Subscription for provisioning information to help with initial
3353 * configuration. This subscription is a one-time query (denoted by the Expires header,
3354 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3355 * configuration, meeting policies, and policy settings that Communicator must enforce.
3356 * TODO: for what we need this information.
3359 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_core_private *sipe_private)
3361 gchar *to = sip_uri_self(sipe_private);
3362 gchar *tmp = get_contact(sipe_private);
3363 gchar *hdr = g_strdup_printf(
3364 "Event: vnd-microsoft-provisioning-v2\r\n"
3365 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3366 "Supported: com.microsoft.autoextend\r\n"
3367 "Supported: ms-benotify\r\n"
3368 "Proxy-Require: ms-benotify\r\n"
3369 "Supported: ms-piggyback-first-notify\r\n"
3370 "Expires: 0\r\n"
3371 "Contact: %s\r\n"
3372 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3373 gchar *body = g_strdup(
3374 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3375 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3376 "<provisioningGroup name=\"ucPolicy\"/>"
3377 "</provisioningGroupList>");
3379 g_free(tmp);
3380 send_sip_request(sipe_private, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3381 g_free(body);
3382 g_free(to);
3383 g_free(hdr);
3386 static void
3387 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3388 gpointer value, gpointer user_data)
3390 struct sip_subscription *subscription = value;
3391 struct sip_dialog *dialog = &subscription->dialog;
3392 struct sipe_core_private *sipe_private = user_data;
3393 gchar *tmp = get_contact(sipe_private);
3394 gchar *hdr = g_strdup_printf(
3395 "Event: %s\r\n"
3396 "Expires: 0\r\n"
3397 "Contact: %s\r\n", subscription->event, tmp);
3398 g_free(tmp);
3400 /* Rate limit to max. 25 requests per seconds */
3401 g_usleep(1000000 / 25);
3403 send_sip_request(sipe_private, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3404 g_free(hdr);
3407 /* IM Session (INVITE and MESSAGE methods) */
3409 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3410 static gchar *
3411 get_end_points (struct sipe_core_private *sipe_private,
3412 struct sip_session *session)
3414 gchar *res;
3416 if (session == NULL) {
3417 return NULL;
3420 res = g_strdup_printf("<sip:%s>", sipe_private->username);
3422 SIPE_DIALOG_FOREACH {
3423 gchar *tmp = res;
3424 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3425 g_free(tmp);
3427 if (dialog->theirepid) {
3428 tmp = res;
3429 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3430 g_free(tmp);
3432 } SIPE_DIALOG_FOREACH_END;
3434 return res;
3437 static gboolean
3438 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
3439 struct sipmsg *msg,
3440 SIPE_UNUSED_PARAMETER struct transaction *trans)
3442 gboolean ret = TRUE;
3444 if (msg->response != 200) {
3445 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
3446 return FALSE;
3449 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
3451 return ret;
3455 * Asks UA/proxy about its capabilities.
3457 static void sipe_options_request(struct sipe_core_private *sipe_private,
3458 const char *who)
3460 gchar *to = sip_uri(who);
3461 gchar *contact = get_contact(sipe_private);
3462 gchar *request = g_strdup_printf(
3463 "Accept: application/sdp\r\n"
3464 "Contact: %s\r\n", contact);
3465 g_free(contact);
3467 send_sip_request(sipe_private, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3469 g_free(to);
3470 g_free(request);
3473 static void
3474 sipe_notify_user(struct sipe_core_private *sipe_private,
3475 struct sip_session *session,
3476 PurpleMessageFlags flags,
3477 const gchar *message)
3479 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3480 PurpleConversation *conv;
3482 if (!session->conv) {
3483 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3484 } else {
3485 conv = session->conv;
3487 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3490 void
3491 sipe_present_info(struct sipe_core_private *sipe_private,
3492 struct sip_session *session,
3493 const gchar *message)
3495 sipe_notify_user(sipe_private, session, PURPLE_MESSAGE_SYSTEM, message);
3498 static void
3499 sipe_present_err(struct sipe_core_private *sipe_private,
3500 struct sip_session *session,
3501 const gchar *message)
3503 sipe_notify_user(sipe_private, session, PURPLE_MESSAGE_ERROR, message);
3506 void
3507 sipe_present_message_undelivered_err(struct sipe_core_private *sipe_private,
3508 struct sip_session *session,
3509 int sip_error,
3510 int sip_warning,
3511 const gchar *who,
3512 const gchar *message)
3514 char *msg, *msg_tmp, *msg_tmp2;
3515 const char *label;
3517 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
3518 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3519 g_free(msg_tmp);
3520 /* Service unavailable; Server Internal Error; Server Time-out */
3521 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
3522 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
3523 g_free(msg);
3524 msg = NULL;
3525 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3526 label = _("This message was not delivered to %s because the service is not available");
3527 } else if (sip_error == 486) { /* Busy Here */
3528 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3529 } else if (sip_error == 415) { /* Unsupported media type */
3530 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
3531 } else {
3532 label = _("This message was not delivered to %s because one or more recipients are offline");
3535 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
3536 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
3537 msg ? ":" : "",
3538 msg ? msg : "");
3539 sipe_present_err(sipe_private, session, msg_tmp);
3540 g_free(msg_tmp2);
3541 g_free(msg_tmp);
3542 g_free(msg);
3546 static gboolean
3547 process_message_response(struct sipe_core_private *sipe_private,
3548 struct sipmsg *msg,
3549 SIPE_UNUSED_PARAMETER struct transaction *trans)
3551 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3552 gboolean ret = TRUE;
3553 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3554 struct sip_session *session = sipe_session_find_im(sipe_private, with);
3555 struct sip_dialog *dialog;
3556 gchar *cseq;
3557 char *key;
3558 struct queued_message *message;
3560 if (!session) {
3561 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
3562 g_free(with);
3563 return FALSE;
3566 dialog = sipe_dialog_find(session, with);
3567 if (!dialog) {
3568 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
3569 g_free(with);
3570 return FALSE;
3573 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3574 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3575 g_free(cseq);
3576 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3578 if (msg->response >= 400) {
3579 PurpleBuddy *pbuddy;
3580 const char *alias = with;
3581 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3582 int warning = -1;
3584 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
3586 if (warn_hdr) {
3587 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3588 if (parts[0]) {
3589 warning = atoi(parts[0]);
3591 g_strfreev(parts);
3594 /* cancel file transfer as rejected by server */
3595 if (msg->response == 606 && /* Not acceptable all. */
3596 warning == 309 && /* Message contents not allowed by policy */
3597 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3599 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
3600 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
3601 sipe_utils_nameval_free(parsed_body);
3604 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3605 alias = purple_buddy_get_alias(pbuddy);
3608 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, (message ? message->body : NULL));
3610 /* drop dangling IM sessions: assume that BYE from remote never reached us */
3611 if (msg->response == 408 || /* Request timeout */
3612 msg->response == 480 || /* Temporarily Unavailable */
3613 msg->response == 481) { /* Call/Transaction Does Not Exist */
3614 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
3615 send_sip_request(sipe_private, "BYE", with, with, NULL, NULL, dialog, NULL);
3618 ret = FALSE;
3619 } else {
3620 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3621 if (message_id) {
3622 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
3623 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
3624 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3627 g_hash_table_remove(session->unconfirmed_messages, key);
3628 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
3629 key, g_hash_table_size(session->unconfirmed_messages));
3632 g_free(key);
3633 g_free(with);
3635 if (ret) sipe_im_process_queue(sipe_private, session);
3636 return ret;
3639 static gboolean
3640 sipe_is_election_finished(struct sip_session *session);
3642 static void
3643 sipe_election_result(struct sipe_core_private *sipe_private,
3644 void *sess);
3646 static gboolean
3647 process_info_response(struct sipe_core_private *sipe_private,
3648 struct sipmsg *msg,
3649 SIPE_UNUSED_PARAMETER struct transaction *trans)
3651 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3652 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3653 struct sip_dialog *dialog;
3654 struct sip_session *session;
3656 session = sipe_session_find_chat_by_callid(sipe_private, callid);
3657 if (!session) {
3658 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
3659 return FALSE;
3662 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
3663 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
3664 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
3665 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
3667 if (xn_request_rm_response) {
3668 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
3669 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
3671 dialog = sipe_dialog_find(session, with);
3672 if (!dialog) {
3673 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
3674 sipe_xml_free(xn_action);
3675 return FALSE;
3678 if (allow && !g_strcasecmp(allow, "true")) {
3679 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
3680 dialog->election_vote = 1;
3681 } else if (allow && !g_strcasecmp(allow, "false")) {
3682 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
3683 dialog->election_vote = -1;
3686 if (sipe_is_election_finished(session)) {
3687 sipe_election_result(sipe_private,
3688 session);
3691 } else if (xn_set_rm_response) {
3694 sipe_xml_free(xn_action);
3698 return TRUE;
3701 static void sipe_send_message(struct sipe_core_private *sipe_private,
3702 struct sip_dialog *dialog,
3703 const char *msg, const char *content_type)
3705 gchar *hdr;
3706 gchar *tmp;
3707 char *msgtext = NULL;
3708 const gchar *msgr = "";
3709 gchar *tmp2 = NULL;
3711 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3712 char *msgformat;
3713 gchar *msgr_value;
3715 sipe_parse_html(msg, &msgformat, &msgtext);
3716 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3718 msgr_value = sipmsg_get_msgr_string(msgformat);
3719 g_free(msgformat);
3720 if (msgr_value) {
3721 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3722 g_free(msgr_value);
3724 } else {
3725 msgtext = g_strdup(msg);
3728 tmp = get_contact(sipe_private);
3729 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3730 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3731 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3732 if (content_type == NULL)
3733 content_type = "text/plain";
3735 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3736 g_free(tmp);
3737 g_free(tmp2);
3739 send_sip_request(sipe_private, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
3740 g_free(msgtext);
3741 g_free(hdr);
3745 void
3746 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3747 struct sip_session * session)
3749 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3750 GSList *entry2 = session->outgoing_message_queue;
3751 while (entry2) {
3752 struct queued_message *msg = entry2->data;
3754 /* for multiparty chat or conference */
3755 if (session->is_multiparty || session->focus_uri) {
3756 gchar *who = sip_uri_self(sipe_private);
3757 serv_got_chat_in(sip->gc, session->chat_id, who,
3758 PURPLE_MESSAGE_SEND, msg->body, time(NULL));
3759 g_free(who);
3762 SIPE_DIALOG_FOREACH {
3763 char *key;
3764 struct queued_message *message;
3766 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3768 message = g_new0(struct queued_message,1);
3769 message->body = g_strdup(msg->body);
3770 if (msg->content_type != NULL)
3771 message->content_type = g_strdup(msg->content_type);
3773 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3774 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3775 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
3776 key, g_hash_table_size(session->unconfirmed_messages));
3777 g_free(key);
3779 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3780 } SIPE_DIALOG_FOREACH_END;
3782 entry2 = sipe_session_dequeue_message(session);
3786 static void
3787 sipe_refer_notify(struct sipe_core_private *sipe_private,
3788 struct sip_session *session,
3789 const gchar *who,
3790 int status,
3791 const gchar *desc)
3793 gchar *hdr;
3794 gchar *body;
3795 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3797 hdr = g_strdup_printf(
3798 "Event: refer\r\n"
3799 "Subscription-State: %s\r\n"
3800 "Content-Type: message/sipfrag\r\n",
3801 status >= 200 ? "terminated" : "active");
3803 body = g_strdup_printf(
3804 "SIP/2.0 %d %s\r\n",
3805 status, desc);
3807 send_sip_request(sipe_private, "NOTIFY", who, who, hdr, body, dialog, NULL);
3809 g_free(hdr);
3810 g_free(body);
3813 static gboolean
3814 process_invite_response(struct sipe_core_private *sipe_private,
3815 struct sipmsg *msg, struct transaction *trans)
3817 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3818 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3819 struct sip_session *session;
3820 struct sip_dialog *dialog;
3821 char *cseq;
3822 char *key;
3823 struct queued_message *message;
3824 struct sipmsg *request_msg = trans->msg;
3826 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3827 gchar *referred_by;
3829 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3830 if (!session) {
3831 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3832 g_free(with);
3833 return FALSE;
3836 dialog = sipe_dialog_find(session, with);
3837 if (!dialog) {
3838 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3839 g_free(with);
3840 return FALSE;
3843 sipe_dialog_parse(dialog, msg, TRUE);
3845 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3846 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3847 g_free(cseq);
3848 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3850 if (msg->response != 200) {
3851 PurpleBuddy *pbuddy;
3852 const char *alias = with;
3853 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3854 int warning = -1;
3856 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3858 if (warn_hdr) {
3859 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3860 if (parts[0]) {
3861 warning = atoi(parts[0]);
3863 g_strfreev(parts);
3866 /* cancel file transfer as rejected by server */
3867 if (msg->response == 606 && /* Not acceptable all. */
3868 warning == 309 && /* Message contents not allowed by policy */
3869 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3871 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3872 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
3873 sipe_utils_nameval_free(parsed_body);
3876 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3877 alias = purple_buddy_get_alias(pbuddy);
3880 if (message) {
3881 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, message->body);
3882 } else {
3883 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3884 sipe_present_err(sipe_private, session, tmp_msg);
3885 g_free(tmp_msg);
3888 sipe_dialog_remove(session, with);
3890 g_free(key);
3891 g_free(with);
3892 return FALSE;
3895 dialog->cseq = 0;
3896 send_sip_request(sipe_private, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3897 dialog->outgoing_invite = NULL;
3898 dialog->is_established = TRUE;
3900 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3901 if (referred_by) {
3902 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3903 g_free(referred_by);
3906 /* add user to chat if it is a multiparty session */
3907 if (session->is_multiparty) {
3908 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3909 with, NULL,
3910 PURPLE_CBFLAGS_NONE, TRUE);
3913 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3914 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3915 sipe_session_dequeue_message(session);
3918 sipe_im_process_queue(sipe_private, session);
3920 g_hash_table_remove(session->unconfirmed_messages, key);
3921 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3922 key, g_hash_table_size(session->unconfirmed_messages));
3924 g_free(key);
3925 g_free(with);
3926 return TRUE;
3930 void
3931 sipe_invite(struct sipe_core_private *sipe_private,
3932 struct sip_session *session,
3933 const gchar *who,
3934 const gchar *msg_body,
3935 const gchar *msg_content_type,
3936 const gchar *referred_by,
3937 const gboolean is_triggered)
3939 gchar *hdr;
3940 gchar *to;
3941 gchar *contact;
3942 gchar *body;
3943 gchar *self;
3944 char *ms_text_format = NULL;
3945 gchar *roster_manager;
3946 gchar *end_points;
3947 gchar *referred_by_str;
3948 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3950 if (dialog && dialog->is_established) {
3951 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3952 return;
3955 if (!dialog) {
3956 dialog = sipe_dialog_add(session);
3957 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3958 dialog->with = g_strdup(who);
3961 if (!(dialog->ourtag)) {
3962 dialog->ourtag = gentag();
3965 to = sip_uri(who);
3967 if (msg_body) {
3968 char *msgtext = NULL;
3969 char *base64_msg;
3970 const gchar *msgr = "";
3971 char *key;
3972 struct queued_message *message;
3973 gchar *tmp = NULL;
3975 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3976 char *msgformat;
3977 gchar *msgr_value;
3979 sipe_parse_html(msg_body, &msgformat, &msgtext);
3980 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3982 msgr_value = sipmsg_get_msgr_string(msgformat);
3983 g_free(msgformat);
3984 if (msgr_value) {
3985 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3986 g_free(msgr_value);
3988 } else {
3989 msgtext = g_strdup(msg_body);
3992 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3993 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3994 msg_content_type ? msg_content_type : "text/plain",
3995 msgr,
3996 base64_msg);
3997 g_free(msgtext);
3998 g_free(tmp);
3999 g_free(base64_msg);
4001 message = g_new0(struct queued_message,1);
4002 message->body = g_strdup(msg_body);
4003 if (msg_content_type != NULL)
4004 message->content_type = g_strdup(msg_content_type);
4006 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
4007 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
4008 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4009 key, g_hash_table_size(session->unconfirmed_messages));
4010 g_free(key);
4013 contact = get_contact(sipe_private);
4014 end_points = get_end_points(sipe_private, session);
4015 self = sip_uri_self(sipe_private);
4016 roster_manager = g_strdup_printf(
4017 "Roster-Manager: %s\r\n"
4018 "EndPoints: %s\r\n",
4019 self,
4020 end_points);
4021 referred_by_str = referred_by ?
4022 g_strdup_printf(
4023 "Referred-By: %s\r\n",
4024 referred_by)
4025 : g_strdup("");
4026 hdr = g_strdup_printf(
4027 "Supported: ms-sender\r\n"
4028 "%s"
4029 "%s"
4030 "%s"
4031 "%s"
4032 "Contact: %s\r\n%s"
4033 "Content-Type: application/sdp\r\n",
4034 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
4035 referred_by_str,
4036 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
4037 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4038 contact,
4039 ms_text_format ? ms_text_format : "");
4040 g_free(ms_text_format);
4041 g_free(self);
4043 body = g_strdup_printf(
4044 "v=0\r\n"
4045 "o=- 0 0 IN IP4 %s\r\n"
4046 "s=session\r\n"
4047 "c=IN IP4 %s\r\n"
4048 "t=0 0\r\n"
4049 "m=%s %d sip null\r\n"
4050 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4051 sipe_backend_network_ip_address(),
4052 sipe_backend_network_ip_address(),
4053 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
4054 sipe_private->server_port);
4056 dialog->outgoing_invite = send_sip_request(sipe_private, "INVITE",
4057 to, to, hdr, body, dialog, process_invite_response);
4059 g_free(to);
4060 g_free(roster_manager);
4061 g_free(end_points);
4062 g_free(referred_by_str);
4063 g_free(body);
4064 g_free(hdr);
4065 g_free(contact);
4068 static void
4069 sipe_refer(struct sipe_core_private *sipe_private,
4070 struct sip_session *session,
4071 const gchar *who)
4073 gchar *hdr;
4074 gchar *contact;
4075 gchar *epid = get_epid(sipe_private);
4076 struct sip_dialog *dialog = sipe_dialog_find(session,
4077 session->roster_manager);
4078 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
4080 contact = get_contact(sipe_private);
4081 hdr = g_strdup_printf(
4082 "Contact: %s\r\n"
4083 "Refer-to: <%s>\r\n"
4084 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4085 "Require: com.microsoft.rtc-multiparty\r\n",
4086 contact,
4087 who,
4088 sipe_private->username,
4089 ourtag ? ";tag=" : "",
4090 ourtag ? ourtag : "",
4091 epid);
4092 g_free(epid);
4094 send_sip_request(sipe_private, "REFER",
4095 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
4097 g_free(hdr);
4098 g_free(contact);
4101 static void
4102 sipe_send_election_request_rm(struct sipe_core_private *sipe_private,
4103 struct sip_dialog *dialog,
4104 int bid)
4106 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4108 gchar *body = g_strdup_printf(
4109 "<?xml version=\"1.0\"?>\r\n"
4110 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4111 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4112 sipe_private->username, bid);
4114 send_sip_request(sipe_private, "INFO",
4115 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4117 g_free(body);
4120 static void
4121 sipe_send_election_set_rm(struct sipe_core_private *sipe_private,
4122 struct sip_dialog *dialog)
4124 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
4126 gchar *body = g_strdup_printf(
4127 "<?xml version=\"1.0\"?>\r\n"
4128 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4129 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4130 sipe_private->username);
4132 send_sip_request(sipe_private, "INFO",
4133 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
4135 g_free(body);
4138 static void
4139 sipe_session_close(struct sipe_core_private *sipe_private,
4140 struct sip_session * session)
4142 if (session && session->focus_uri) {
4143 sipe_conf_immcu_closed(sipe_private, session);
4144 conf_session_close(sipe_private, session);
4147 if (session) {
4148 SIPE_DIALOG_FOREACH {
4149 /* @TODO slow down BYE message sending rate */
4150 /* @see single subscription code */
4151 send_sip_request(sipe_private, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
4152 } SIPE_DIALOG_FOREACH_END;
4154 sipe_session_remove(sipe_private, session);
4158 static void
4159 sipe_session_close_all(struct sipe_core_private *sipe_private)
4161 GSList *entry;
4162 while ((entry = sipe_private->sessions) != NULL) {
4163 sipe_session_close(sipe_private, entry->data);
4167 void
4168 sipe_convo_closed(PurpleConnection * gc, const char *who)
4170 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
4172 SIPE_DEBUG_INFO("conversation with %s closed", who);
4173 sipe_session_close(sipe_private,
4174 sipe_session_find_im(sipe_private, who));
4177 void
4178 sipe_chat_leave (PurpleConnection *gc, int id)
4180 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
4181 struct sip_session *session = sipe_session_find_chat_by_id(sipe_private,
4182 id);
4184 sipe_session_close(sipe_private, session);
4187 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
4188 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4190 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
4191 struct sip_session *session;
4192 struct sip_dialog *dialog;
4193 gchar *uri = sip_uri(who);
4195 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
4197 session = sipe_session_find_or_add_im(sipe_private, uri);
4198 dialog = sipe_dialog_find(session, uri);
4200 // Queue the message
4201 sipe_session_enqueue_message(session, what, NULL);
4203 if (dialog && !dialog->outgoing_invite) {
4204 sipe_im_process_queue(sipe_private, session);
4205 } else if (!dialog || !dialog->outgoing_invite) {
4206 // Need to send the INVITE to get the outgoing dialog setup
4207 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
4210 g_free(uri);
4211 return 1;
4214 int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
4215 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
4217 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
4218 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4219 struct sip_session *session;
4221 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
4223 session = sipe_session_find_chat_by_id(sipe_private, id);
4225 // Queue the message
4226 if (session && session->dialogs) {
4227 sipe_session_enqueue_message(session,what,NULL);
4228 sipe_im_process_queue(sipe_private, session);
4229 } else if (sip) {
4230 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
4231 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
4233 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
4234 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
4236 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
4237 struct sip_session *session = sipe_session_add_chat(sipe_private);
4239 session->is_multiparty = FALSE;
4240 session->focus_uri = g_strdup(proto_chat_id);
4241 sipe_session_enqueue_message(session, what, NULL);
4242 sipe_invite_conf_focus(sipe_private, session);
4246 return 1;
4249 /* End IM Session (INVITE and MESSAGE methods) */
4251 static void process_incoming_info(struct sipe_core_private *sipe_private,
4252 struct sipmsg *msg)
4254 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4255 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4256 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4257 gchar *from;
4258 struct sip_session *session;
4260 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg->body ? msg->body : "");
4262 /* Call Control protocol */
4263 if (g_str_has_prefix(contenttype, "application/csta+xml"))
4265 process_incoming_info_csta(sipe_private, msg);
4266 return;
4269 from = parse_from(sipmsg_find_header(msg, "From"));
4270 session = sipe_session_find_chat_or_im(sipe_private, callid, from);
4271 if (!session) {
4272 g_free(from);
4273 return;
4276 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
4278 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
4279 const sipe_xml *xn_request_rm = sipe_xml_child(xn_action, "RequestRM");
4280 const sipe_xml *xn_set_rm = sipe_xml_child(xn_action, "SetRM");
4282 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
4284 if (xn_request_rm) {
4285 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
4286 int bid = sipe_xml_int_attribute(xn_request_rm, "bid", 0);
4287 gchar *body = g_strdup_printf(
4288 "<?xml version=\"1.0\"?>\r\n"
4289 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4290 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4291 sipe_private->username,
4292 session->bid < bid ? "true" : "false");
4293 send_sip_response(sipe_private, msg, 200, "OK", body);
4294 g_free(body);
4295 } else if (xn_set_rm) {
4296 gchar *body;
4297 const char *rm = sipe_xml_attribute(xn_set_rm, "uri");
4298 g_free(session->roster_manager);
4299 session->roster_manager = g_strdup(rm);
4301 body = g_strdup_printf(
4302 "<?xml version=\"1.0\"?>\r\n"
4303 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4304 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4305 sipe_private->username);
4306 send_sip_response(sipe_private, msg, 200, "OK", body);
4307 g_free(body);
4309 sipe_xml_free(xn_action);
4312 else
4314 /* looks like purple lacks typing notification for chat */
4315 if (!session->is_multiparty && !session->focus_uri) {
4316 sipe_xml *xn_keyboard_activity = sipe_xml_parse(msg->body, msg->bodylen);
4317 const char *status = sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity, "status"),
4318 "status");
4319 if (sipe_strequal(status, "type")) {
4320 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4321 } else if (sipe_strequal(status, "idle")) {
4322 serv_got_typing_stopped(sip->gc, from);
4324 sipe_xml_free(xn_keyboard_activity);
4327 send_sip_response(sipe_private, msg, 200, "OK", NULL);
4329 g_free(from);
4332 static void process_incoming_cancel(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
4333 SIPE_UNUSED_PARAMETER struct sipmsg *msg)
4335 #if HAVE_VV
4336 struct sipe_media_call_private *call_private = sipe_private->media_call;
4337 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4338 if (call_private &&
4339 sipe_strequal(sipe_media_get_callid(call_private), callid)) {
4340 struct sip_session *session = sipe_session_find_chat_by_callid(sipe_private,
4341 callid);
4342 sipe_media_hangup(sipe_private);
4343 if (session) {
4344 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4345 sipe_dialog_remove(session, from);
4346 g_free(from);
4348 sipe_session_close(sipe_private, session);
4351 #endif
4354 static void process_incoming_bye(struct sipe_core_private *sipe_private,
4355 struct sipmsg *msg)
4357 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4358 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4359 struct sip_session *session;
4360 struct sip_dialog *dialog;
4362 #if HAVE_VV
4364 struct sipe_media_call_private *call_private = sipe_private->media_call;
4365 if (call_private &&
4366 sipe_strequal(sipe_media_get_callid(call_private), callid)) {
4367 // BYE ends a media call
4368 sipe_media_hangup(sipe_private);
4371 #endif
4373 /* collect dialog identification
4374 * we need callid, ourtag and theirtag to unambiguously identify dialog
4376 /* take data before 'msg' will be modified by send_sip_response */
4377 dialog = g_new0(struct sip_dialog, 1);
4378 dialog->callid = g_strdup(callid);
4379 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
4380 dialog->with = g_strdup(from);
4381 sipe_dialog_parse(dialog, msg, FALSE);
4383 send_sip_response(sipe_private, msg, 200, "OK", NULL);
4385 session = sipe_session_find_chat_or_im(sipe_private, callid, from);
4386 if (!session) {
4387 sipe_dialog_free(dialog);
4388 g_free(from);
4389 return;
4392 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
4393 g_free(session->roster_manager);
4394 session->roster_manager = NULL;
4397 /* This what BYE is essentially for - terminating dialog */
4398 sipe_dialog_remove_3(session, dialog);
4399 sipe_dialog_free(dialog);
4400 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
4401 sipe_conf_immcu_closed(sipe_private, session);
4402 } else if (session->is_multiparty) {
4403 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
4406 g_free(from);
4409 static void process_incoming_refer(struct sipe_core_private *sipe_private,
4410 struct sipmsg *msg)
4412 gchar *self = sip_uri_self(sipe_private);
4413 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4414 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4415 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
4416 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
4417 struct sip_session *session;
4418 struct sip_dialog *dialog;
4420 session = sipe_session_find_chat_by_callid(sipe_private, callid);
4421 dialog = sipe_dialog_find(session, from);
4423 if (!session || !dialog || !session->roster_manager || !sipe_strcase_equal(session->roster_manager, self)) {
4424 send_sip_response(sipe_private, msg, 500, "Server Internal Error", NULL);
4425 } else {
4426 send_sip_response(sipe_private, msg, 202, "Accepted", NULL);
4428 sipe_invite(sipe_private, session, refer_to, NULL, NULL, referred_by, FALSE);
4431 g_free(self);
4432 g_free(from);
4433 g_free(refer_to);
4434 g_free(referred_by);
4437 unsigned int
4438 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
4440 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
4441 struct sip_session *session;
4442 struct sip_dialog *dialog;
4444 if (state == PURPLE_NOT_TYPING)
4445 return 0;
4447 session = sipe_session_find_im(sipe_private, who);
4448 dialog = sipe_dialog_find(session, who);
4450 if (session && dialog && dialog->is_established) {
4451 send_sip_request(sipe_private, "INFO", who, who,
4452 "Content-Type: application/xml\r\n",
4453 SIPE_SEND_TYPING, dialog, NULL);
4455 return SIPE_TYPING_SEND_TIMEOUT;
4458 static void do_reauthenticate_cb(struct sipe_core_private *sipe_private,
4459 SIPE_UNUSED_PARAMETER gpointer unused)
4461 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4462 /* register again when security token expires */
4463 /* we have to start a new authentication as the security token
4464 * is almost expired by sending a not signed REGISTER message */
4465 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
4466 sipe_auth_free(&sip->registrar);
4467 sipe_auth_free(&sip->proxy);
4468 sip->registerstatus = 0;
4469 do_register(sipe_private);
4470 sip->reauthenticate_set = FALSE;
4473 static gboolean
4474 sipe_process_incoming_x_msmsgsinvite(struct sipe_core_private *sipe_private,
4475 struct sipmsg *msg,
4476 GSList *parsed_body)
4478 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4479 gboolean found = FALSE;
4481 if (parsed_body) {
4482 const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command");
4484 if (sipe_strequal(invitation_command, "INVITE")) {
4485 sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body);
4486 found = TRUE;
4487 } else if (sipe_strequal(invitation_command, "CANCEL")) {
4488 sipe_ft_incoming_cancel(sip->gc->account, parsed_body);
4489 found = TRUE;
4490 } else if (sipe_strequal(invitation_command, "ACCEPT")) {
4491 sipe_ft_incoming_accept(sip->gc->account, parsed_body);
4492 found = TRUE;
4495 return found;
4498 static void process_incoming_message(struct sipe_core_private *sipe_private,
4499 struct sipmsg *msg)
4501 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4502 gchar *from;
4503 const gchar *contenttype;
4504 gboolean found = FALSE;
4506 from = parse_from(sipmsg_find_header(msg, "From"));
4508 if (!from) return;
4510 SIPE_DEBUG_INFO("got message from %s: %s", from, msg->body);
4512 contenttype = sipmsg_find_header(msg, "Content-Type");
4513 if (g_str_has_prefix(contenttype, "text/plain")
4514 || g_str_has_prefix(contenttype, "text/html")
4515 || g_str_has_prefix(contenttype, "multipart/related")
4516 || g_str_has_prefix(contenttype, "multipart/alternative"))
4518 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4519 gchar *html = get_html_message(contenttype, msg->body);
4521 struct sip_session *session = sipe_session_find_chat_or_im(sipe_private,
4522 callid,
4523 from);
4524 if (session && session->focus_uri) { /* a conference */
4525 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4526 gchar *sender = parse_from(tmp);
4527 g_free(tmp);
4528 serv_got_chat_in(sip->gc, session->chat_id, sender,
4529 PURPLE_MESSAGE_RECV, html, time(NULL));
4530 g_free(sender);
4531 } else if (session && session->is_multiparty) { /* a multiparty chat */
4532 serv_got_chat_in(sip->gc, session->chat_id, from,
4533 PURPLE_MESSAGE_RECV, html, time(NULL));
4534 } else {
4535 serv_got_im(sip->gc, from, html, 0, time(NULL));
4537 g_free(html);
4538 send_sip_response(sipe_private, msg, 200, "OK", NULL);
4539 found = TRUE;
4541 } else if (g_str_has_prefix(contenttype, "application/im-iscomposing+xml")) {
4542 sipe_xml *isc = sipe_xml_parse(msg->body, msg->bodylen);
4543 const sipe_xml *state;
4544 gchar *statedata;
4546 if (!isc) {
4547 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
4548 g_free(from);
4549 return;
4552 state = sipe_xml_child(isc, "state");
4554 if (!state) {
4555 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
4556 sipe_xml_free(isc);
4557 g_free(from);
4558 return;
4561 statedata = sipe_xml_data(state);
4562 if (statedata) {
4563 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4564 else serv_got_typing_stopped(sip->gc, from);
4566 g_free(statedata);
4568 sipe_xml_free(isc);
4569 send_sip_response(sipe_private, msg, 200, "OK", NULL);
4570 found = TRUE;
4571 } else if (g_str_has_prefix(contenttype, "text/x-msmsgsinvite")) {
4572 GSList *body = sipe_ft_parse_msg_body(msg->body);
4573 found = sipe_process_incoming_x_msmsgsinvite(sipe_private, msg, body);
4574 sipe_utils_nameval_free(body);
4575 if (found) {
4576 send_sip_response(sipe_private, msg, 200, "OK", NULL);
4579 if (!found) {
4580 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4581 struct sip_session *session = sipe_session_find_chat_or_im(sipe_private,
4582 callid,
4583 from);
4584 if (session) {
4585 gchar *errmsg = g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4586 from);
4587 sipe_present_err(sipe_private, session, errmsg);
4588 g_free(errmsg);
4591 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype);
4592 send_sip_response(sipe_private, msg, 415, "Unsupported media type", NULL);
4594 g_free(from);
4597 #ifdef HAVE_VV
4598 static void sipe_invite_mime_cb(gpointer user_data, const GSList *fields,
4599 const gchar *body, SIPE_UNUSED_PARAMETER gsize length)
4601 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4602 const gchar *cd = sipe_utils_nameval_find(fields, "Content-Disposition");
4604 if (!g_str_has_prefix(type, "application/sdp"))
4605 return;
4607 if (cd && !strstr(cd, "ms-proxy-2007fallback")) {
4608 struct sipmsg *msg = user_data;
4609 const gchar* msg_ct = sipmsg_find_header(msg, "Content-Type");
4611 if (g_str_has_prefix(msg_ct, "application/sdp")) {
4612 /* We have already found suitable alternative and set message's body
4613 * and Content-Type accordingly */
4614 return;
4617 sipmsg_remove_header_now(msg, "Content-Type");
4618 sipmsg_add_header_now(msg, "Content-Type", type);
4620 /* Replace message body with chosen alternative, so we can continue to
4621 * process it as a normal single part message. */
4622 g_free(msg->body);
4623 msg->body = g_strndup(body, length);
4626 #endif
4628 static void process_incoming_invite(struct sipe_core_private *sipe_private,
4629 struct sipmsg *msg)
4631 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4632 gchar *body;
4633 gchar *newTag;
4634 const gchar *oldHeader;
4635 gchar *newHeader;
4636 gboolean is_multiparty = FALSE;
4637 gboolean is_triggered = FALSE;
4638 gboolean was_multiparty = TRUE;
4639 gboolean just_joined = FALSE;
4640 gchar *from;
4641 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
4642 const gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4643 const gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4644 const gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4645 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4646 GSList *end_points = NULL;
4647 char *tmp = NULL;
4648 struct sip_session *session;
4649 const gchar *ms_text_format;
4651 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg->body ? tmp = fix_newlines(msg->body) : "");
4652 g_free(tmp);
4654 #ifdef HAVE_VV
4655 if (g_str_has_prefix(content_type, "multipart/alternative")) {
4656 sipe_mime_parts_foreach(content_type, msg->body, sipe_invite_mime_cb, msg);
4658 #endif
4660 /* Invitation to join conference */
4661 if (g_str_has_prefix(content_type, "application/ms-conf-invite+xml")) {
4662 process_incoming_invite_conf(sipe_private, msg);
4663 return;
4666 #ifdef HAVE_VV
4667 /* Invitation to audio call */
4668 if (msg->body && strstr(msg->body, "m=audio")) {
4669 sipe_media_incoming_invite(sipe_private, msg);
4670 return;
4672 #endif
4674 /* Only accept text invitations */
4675 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4676 send_sip_response(sipe_private, msg, 501, "Not implemented", NULL);
4677 return;
4680 // TODO There *must* be a better way to clean up the To header to add a tag...
4681 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
4682 oldHeader = sipmsg_find_header(msg, "To");
4683 newTag = gentag();
4684 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4685 sipmsg_remove_header_now(msg, "To");
4686 sipmsg_add_header_now(msg, "To", newHeader);
4687 g_free(newHeader);
4689 if (end_points_hdr) {
4690 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4692 if (g_slist_length(end_points) > 2) {
4693 is_multiparty = TRUE;
4696 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4697 is_triggered = TRUE;
4698 is_multiparty = TRUE;
4701 session = sipe_session_find_chat_by_callid(sipe_private, callid);
4702 /* Convert to multiparty */
4703 if (session && is_multiparty && !session->is_multiparty) {
4704 g_free(session->with);
4705 session->with = NULL;
4706 was_multiparty = FALSE;
4707 session->is_multiparty = TRUE;
4708 session->chat_id = rand();
4711 if (!session && is_multiparty) {
4712 session = sipe_session_find_or_add_chat_by_callid(sipe_private,
4713 callid);
4715 /* IM session */
4716 from = parse_from(sipmsg_find_header(msg, "From"));
4717 if (!session) {
4718 session = sipe_session_find_or_add_im(sipe_private, from);
4721 if (session) {
4722 g_free(session->callid);
4723 session->callid = g_strdup(callid);
4725 session->is_multiparty = is_multiparty;
4726 if (roster_manager) {
4727 session->roster_manager = g_strdup(roster_manager);
4731 if (is_multiparty && end_points) {
4732 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4733 GSList *entry = end_points;
4734 while (entry) {
4735 struct sip_dialog *dialog;
4736 struct sipendpoint *end_point = entry->data;
4737 entry = entry->next;
4739 if (!g_strcasecmp(from, end_point->contact) ||
4740 !g_strcasecmp(to, end_point->contact))
4741 continue;
4743 dialog = sipe_dialog_find(session, end_point->contact);
4744 if (dialog) {
4745 g_free(dialog->theirepid);
4746 dialog->theirepid = end_point->epid;
4747 end_point->epid = NULL;
4748 } else {
4749 dialog = sipe_dialog_add(session);
4751 dialog->callid = g_strdup(session->callid);
4752 dialog->with = end_point->contact;
4753 end_point->contact = NULL;
4754 dialog->theirepid = end_point->epid;
4755 end_point->epid = NULL;
4757 just_joined = TRUE;
4759 /* send triggered INVITE */
4760 sipe_invite(sipe_private, session, dialog->with, NULL, NULL, NULL, TRUE);
4763 g_free(to);
4766 if (end_points) {
4767 GSList *entry = end_points;
4768 while (entry) {
4769 struct sipendpoint *end_point = entry->data;
4770 entry = entry->next;
4771 g_free(end_point->contact);
4772 g_free(end_point->epid);
4773 g_free(end_point);
4775 g_slist_free(end_points);
4778 if (session) {
4779 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4780 if (dialog) {
4781 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
4782 sipe_dialog_parse_routes(dialog, msg, FALSE);
4783 } else {
4784 dialog = sipe_dialog_add(session);
4786 dialog->callid = g_strdup(session->callid);
4787 dialog->with = g_strdup(from);
4788 sipe_dialog_parse(dialog, msg, FALSE);
4790 if (!dialog->ourtag) {
4791 dialog->ourtag = newTag;
4792 newTag = NULL;
4795 just_joined = TRUE;
4797 } else {
4798 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
4800 g_free(newTag);
4802 if (is_multiparty && !session->conv) {
4803 gchar *chat_title = sipe_chat_get_name(callid);
4804 gchar *self = sip_uri_self(sipe_private);
4805 /* create prpl chat */
4806 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4807 session->chat_title = g_strdup(chat_title);
4808 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4809 /* add self */
4810 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4811 self, NULL,
4812 PURPLE_CBFLAGS_NONE, FALSE);
4813 g_free(chat_title);
4814 g_free(self);
4817 if (is_multiparty && !was_multiparty) {
4818 /* add current IM counterparty to chat */
4819 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4820 sipe_dialog_first(session)->with, NULL,
4821 PURPLE_CBFLAGS_NONE, FALSE);
4824 /* add inviting party to chat */
4825 if (just_joined && session->conv) {
4826 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4827 from, NULL,
4828 PURPLE_CBFLAGS_NONE, TRUE);
4831 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4833 /* This used only in 2005 official client, not 2007 or Reuters.
4834 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4835 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4837 /* also enabled for 2005 file transfer. Didn't work otherwise. */
4838 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4839 if (is_multiparty ||
4840 (ms_text_format && g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite")) )
4842 if (ms_text_format) {
4843 if (g_str_has_prefix(ms_text_format, "text/x-msmsgsinvite"))
4845 gchar *tmp = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
4846 if (tmp) {
4847 gsize len;
4848 gchar *body = (gchar *) g_base64_decode(tmp, &len);
4850 GSList *parsed_body = sipe_ft_parse_msg_body(body);
4852 sipe_process_incoming_x_msmsgsinvite(sipe_private, msg, parsed_body);
4853 sipe_utils_nameval_free(parsed_body);
4854 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4856 g_free(tmp);
4858 else if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html"))
4860 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4861 gchar *html = get_html_message(ms_text_format, NULL);
4862 if (html) {
4863 if (is_multiparty) {
4864 serv_got_chat_in(sip->gc, session->chat_id, from,
4865 PURPLE_MESSAGE_RECV, html, time(NULL));
4866 } else {
4867 serv_got_im(sip->gc, from, html, 0, time(NULL));
4869 g_free(html);
4870 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4876 g_free(from);
4878 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
4879 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sipe_private));
4880 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4882 body = g_strdup_printf(
4883 "v=0\r\n"
4884 "o=- 0 0 IN IP4 %s\r\n"
4885 "s=session\r\n"
4886 "c=IN IP4 %s\r\n"
4887 "t=0 0\r\n"
4888 "m=%s %d sip sip:%s\r\n"
4889 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4890 sipe_backend_network_ip_address(),
4891 sipe_backend_network_ip_address(),
4892 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
4893 sipe_private->server_port,
4894 sipe_private->username);
4895 send_sip_response(sipe_private, msg, 200, "OK", body);
4896 g_free(body);
4899 static void process_incoming_options(struct sipe_core_private *sipe_private,
4900 struct sipmsg *msg)
4902 gchar *body;
4904 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4905 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sipe_private));
4906 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4908 body = g_strdup_printf(
4909 "v=0\r\n"
4910 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4911 "s=session\r\n"
4912 "c=IN IP4 0.0.0.0\r\n"
4913 "t=0 0\r\n"
4914 "m=%s %d sip sip:%s\r\n"
4915 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
4916 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
4917 sipe_private->server_port,
4918 sipe_private->username);
4919 send_sip_response(sipe_private, msg, 200, "OK", body);
4920 g_free(body);
4923 static const char*
4924 sipe_get_auth_scheme_name(struct sipe_core_private *sipe_private)
4926 const char *res = "NTLM";
4927 #ifdef HAVE_LIBKRB5
4928 if (SIPE_CORE_PUBLIC_FLAG_IS(KRB5)) {
4929 res = "Kerberos";
4931 #else
4932 (void) sipe_private; /* make compiler happy */
4933 #endif
4934 return res;
4937 static void sipe_connection_cleanup(struct sipe_core_private *sipe_private);
4939 gboolean process_register_response(struct sipe_core_private *sipe_private,
4940 struct sipmsg *msg,
4941 SIPE_UNUSED_PARAMETER struct transaction *trans)
4943 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4944 gchar *tmp;
4945 const gchar *expires_header;
4946 int expires, i;
4947 GSList *hdr = msg->headers;
4948 struct sipnameval *elem;
4950 expires_header = sipmsg_find_header(msg, "Expires");
4951 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
4952 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires);
4954 switch (msg->response) {
4955 case 200:
4956 if (expires == 0) {
4957 sip->registerstatus = 0;
4958 } else {
4959 const gchar *contact_hdr;
4960 gchar *gruu = NULL;
4961 gchar *epid;
4962 gchar *uuid;
4963 gchar *timeout;
4964 const gchar *server_hdr = sipmsg_find_header(msg, "Server");
4965 const char *auth_scheme;
4967 if (!sip->reregister_set) {
4968 gchar *action_name = g_strdup_printf("<%s>", "registration");
4969 sipe_schedule_seconds(sipe_private,
4970 action_name,
4971 NULL,
4972 expires,
4973 do_register_cb,
4974 NULL);
4975 g_free(action_name);
4976 sip->reregister_set = TRUE;
4979 sip->registerstatus = 3;
4981 if (server_hdr && !sipe_private->server_version) {
4982 sipe_private->server_version = g_strdup(server_hdr);
4983 g_free(sipe_private->useragent);
4984 sipe_private->useragent = NULL;
4987 auth_scheme = sipe_get_auth_scheme_name(sipe_private);
4988 tmp = sipmsg_find_auth_header(msg, auth_scheme);
4990 if (tmp) {
4991 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp);
4992 fill_auth(tmp, &sip->registrar);
4995 if (!sip->reauthenticate_set) {
4996 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
4997 guint reauth_timeout;
4998 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
4999 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5000 reauth_timeout = sip->registrar.expires - 300;
5001 } else {
5002 /* NTLM: we have to reauthenticate as our security token expires
5003 after eight hours (be five minutes early) */
5004 reauth_timeout = (8 * 3600) - 300;
5006 sipe_schedule_seconds(sipe_private,
5007 action_name,
5008 NULL,
5009 reauth_timeout,
5010 do_reauthenticate_cb,
5011 NULL);
5012 g_free(action_name);
5013 sip->reauthenticate_set = TRUE;
5016 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
5018 epid = get_epid(sipe_private);
5019 uuid = generateUUIDfromEPID(epid);
5020 g_free(epid);
5022 // There can be multiple Contact headers (one per location where the user is logged in) so
5023 // make sure to only get the one for this uuid
5024 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
5025 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
5026 if (valid_contact) {
5027 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
5028 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5029 g_free(valid_contact);
5030 break;
5031 } else {
5032 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5035 g_free(uuid);
5037 g_free(sipe_private->contact);
5038 if(gruu) {
5039 sipe_private->contact = g_strdup_printf("<%s>", gruu);
5040 g_free(gruu);
5041 } else {
5042 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5043 sip_transport_default_contact(sipe_private);
5045 SIPE_CORE_PRIVATE_FLAG_UNSET(OCS2007);
5046 sip->batched_support = FALSE;
5048 while(hdr)
5050 elem = hdr->data;
5051 if (sipe_strcase_equal(elem->name, "Supported")) {
5052 if (sipe_strcase_equal(elem->value, "msrtc-event-categories")) {
5053 /* We interpret this as OCS2007+ indicator */
5054 SIPE_CORE_PRIVATE_FLAG_SET(OCS2007);
5055 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem->value);
5057 if (sipe_strcase_equal(elem->value, "adhoclist")) {
5058 sip->batched_support = TRUE;
5059 SIPE_DEBUG_INFO("Supported: %s", elem->value);
5062 if (sipe_strcase_equal(elem->name, "Allow-Events")){
5063 gchar **caps = g_strsplit(elem->value,",",0);
5064 i = 0;
5065 while (caps[i]) {
5066 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
5067 SIPE_DEBUG_INFO("Allow-Events: %s", caps[i]);
5068 i++;
5070 g_strfreev(caps);
5072 hdr = g_slist_next(hdr);
5075 /* rejoin open chats to be able to use them by continue to send messages */
5076 purple_conversation_foreach(sipe_rejoin_chat);
5078 /* subscriptions */
5079 if (!sip->subscribed) { //do it just once, not every re-register
5081 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
5082 (GCompareFunc)g_ascii_strcasecmp)) {
5083 sipe_subscribe_roaming_contacts(sipe_private);
5086 /* For 2007+ it does not make sence to subscribe to:
5087 * vnd-microsoft-roaming-ACL
5088 * vnd-microsoft-provisioning (not v2)
5089 * presence.wpending
5090 * These are for backward compatibility.
5092 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
5094 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
5095 (GCompareFunc)g_ascii_strcasecmp)) {
5096 sipe_subscribe_roaming_self(sipe_private);
5098 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
5099 (GCompareFunc)g_ascii_strcasecmp)) {
5100 sipe_subscribe_roaming_provisioning_v2(sipe_private);
5103 /* For 2005- servers */
5104 else
5106 //sipe_options_request(sip, sipe_private->public.sip_domain);
5108 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
5109 (GCompareFunc)g_ascii_strcasecmp)) {
5110 sipe_subscribe_roaming_acl(sipe_private);
5112 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
5113 (GCompareFunc)g_ascii_strcasecmp)) {
5114 sipe_subscribe_roaming_provisioning(sipe_private);
5116 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
5117 (GCompareFunc)g_ascii_strcasecmp)) {
5118 sipe_subscribe_presence_wpending(sipe_private,
5119 msg);
5122 /* For 2007+ we publish our initial statuses and calendar data only after
5123 * received our existing publications in sipe_process_roaming_self()
5124 * Only in this case we know versions of current publications made
5125 * on our behalf.
5127 /* For 2005- we publish our initial statuses only after
5128 * received our existing UserInfo data in response to
5129 * self subscription.
5130 * Only in this case we won't override existing UserInfo data
5131 * set earlier or by other client on our behalf.
5135 sip->subscribed = TRUE;
5138 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
5139 "timeout=", ";", NULL);
5140 if (timeout != NULL) {
5141 sscanf(timeout, "%u", &sipe_private->public.keepalive_timeout);
5142 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5143 sipe_private->public.keepalive_timeout);
5144 g_free(timeout);
5147 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip->cseq);
5149 break;
5150 case 301:
5152 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
5154 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
5155 gchar **parts = g_strsplit(redirect + 4, ";", 0);
5156 gchar **tmp;
5157 gchar *hostname;
5158 int port = 0;
5159 guint transport = SIPE_TRANSPORT_TLS;
5160 int i = 1;
5162 tmp = g_strsplit(parts[0], ":", 0);
5163 hostname = g_strdup(tmp[0]);
5164 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
5165 g_strfreev(tmp);
5167 while (parts[i]) {
5168 tmp = g_strsplit(parts[i], "=", 0);
5169 if (tmp[1]) {
5170 if (g_strcasecmp("transport", tmp[0]) == 0) {
5171 if (g_strcasecmp("tcp", tmp[1]) == 0) {
5172 transport = SIPE_TRANSPORT_TCP;
5176 g_strfreev(tmp);
5177 i++;
5179 g_strfreev(parts);
5181 /* Close old connection */
5182 sipe_connection_cleanup(sipe_private);
5184 /* Create new connection */
5185 sipe_server_register(sipe_private, transport, hostname, port);
5186 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %d",
5187 hostname, port, transport);
5189 g_free(redirect);
5191 break;
5192 case 401:
5193 if (sip->registerstatus != 2) {
5194 const char *auth_scheme;
5195 SIPE_DEBUG_INFO("REGISTER retries %d", sip->registrar.retries);
5196 if (sip->registrar.retries > 3) {
5197 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
5198 SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
5199 _("Authentication failed"));
5200 return TRUE;
5203 auth_scheme = sipe_get_auth_scheme_name(sipe_private);
5204 tmp = sipmsg_find_auth_header(msg, auth_scheme);
5206 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp ? tmp : "");
5207 if (!tmp) {
5208 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
5209 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
5210 SIPE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
5211 tmp2);
5212 g_free(tmp2);
5213 return TRUE;
5215 fill_auth(tmp, &sip->registrar);
5216 sip->registerstatus = 2;
5217 if (sip->account->disconnecting) {
5218 do_register_exp(sipe_private, 0);
5219 } else {
5220 do_register(sipe_private);
5223 break;
5224 case 403:
5226 const gchar *diagnostics = sipmsg_find_header(msg, "Warning");
5227 gchar **reason = NULL;
5228 gchar *warning;
5229 if (diagnostics != NULL) {
5230 /* Example header:
5231 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5233 reason = g_strsplit(diagnostics, "\"", 0);
5235 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
5236 (reason && reason[1]) ? reason[1] : _("no reason given"));
5237 g_strfreev(reason);
5239 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
5240 SIPE_CONNECTION_ERROR_INVALID_SETTINGS,
5241 warning);
5242 g_free(warning);
5243 return TRUE;
5245 break;
5246 case 404:
5248 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5249 gchar *reason = NULL;
5250 gchar *warning;
5251 if (diagnostics != NULL) {
5252 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5254 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5255 diagnostics ? (reason ? reason : _("no reason given")) :
5256 _("SIP is either not enabled for the destination URI or it does not exist"));
5257 g_free(reason);
5259 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
5260 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
5261 warning);
5262 g_free(warning);
5263 return TRUE;
5265 break;
5266 case 503:
5267 case 504: /* Server time-out */
5269 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
5270 gchar *reason = NULL;
5271 gchar *warning;
5272 if (diagnostics != NULL) {
5273 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
5275 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
5276 g_free(reason);
5278 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
5279 SIPE_CONNECTION_ERROR_NETWORK,
5280 warning);
5281 g_free(warning);
5282 return TRUE;
5284 break;
5286 return TRUE;
5290 * Returns 2005-style activity and Availability.
5292 * @param status Sipe statis id.
5294 static void
5295 sipe_get_act_avail_by_status_2005(const char *status,
5296 int *activity,
5297 int *availability)
5299 int avail = 300; /* online */
5300 int act = 400; /* Available */
5302 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
5303 act = 100;
5304 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
5305 // act = 150;
5306 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
5307 act = 300;
5308 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
5309 act = 400;
5310 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
5311 // act = 500;
5312 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
5313 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
5314 act = 600;
5315 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
5316 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
5317 avail = 0; /* offline */
5318 act = 100;
5319 } else {
5320 act = 400; /* Available */
5323 if (activity) *activity = act;
5324 if (availability) *availability = avail;
5328 * [MS-SIP] 2.2.1
5330 * @param activity 2005 aggregated activity. Ex.: 600
5331 * @param availablity 2005 aggregated availablity. Ex.: 300
5333 static const char *
5334 sipe_get_status_by_act_avail_2005(const int activity,
5335 const int availablity,
5336 char **activity_desc)
5338 const char *status_id = NULL;
5339 const char *act = NULL;
5341 if (activity < 150) {
5342 status_id = SIPE_STATUS_ID_AWAY;
5343 } else if (activity < 200) {
5344 //status_id = SIPE_STATUS_ID_LUNCH;
5345 status_id = SIPE_STATUS_ID_AWAY;
5346 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
5347 } else if (activity < 300) {
5348 //status_id = SIPE_STATUS_ID_IDLE;
5349 status_id = SIPE_STATUS_ID_AWAY;
5350 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5351 } else if (activity < 400) {
5352 status_id = SIPE_STATUS_ID_BRB;
5353 } else if (activity < 500) {
5354 status_id = SIPE_STATUS_ID_AVAILABLE;
5355 } else if (activity < 600) {
5356 //status_id = SIPE_STATUS_ID_ON_PHONE;
5357 status_id = SIPE_STATUS_ID_BUSY;
5358 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
5359 } else if (activity < 700) {
5360 status_id = SIPE_STATUS_ID_BUSY;
5361 } else if (activity < 800) {
5362 status_id = SIPE_STATUS_ID_AWAY;
5363 } else {
5364 status_id = SIPE_STATUS_ID_AVAILABLE;
5367 if (availablity < 100)
5368 status_id = SIPE_STATUS_ID_OFFLINE;
5370 if (activity_desc && act) {
5371 g_free(*activity_desc);
5372 *activity_desc = g_strdup(act);
5375 return status_id;
5379 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5381 static const char*
5382 sipe_get_status_by_availability(int avail,
5383 char** activity_desc)
5385 const char *status;
5386 const char *act = NULL;
5388 if (avail < 3000) {
5389 status = SIPE_STATUS_ID_OFFLINE;
5390 } else if (avail < 4500) {
5391 status = SIPE_STATUS_ID_AVAILABLE;
5392 } else if (avail < 6000) {
5393 //status = SIPE_STATUS_ID_IDLE;
5394 status = SIPE_STATUS_ID_AVAILABLE;
5395 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
5396 } else if (avail < 7500) {
5397 status = SIPE_STATUS_ID_BUSY;
5398 } else if (avail < 9000) {
5399 //status = SIPE_STATUS_ID_BUSYIDLE;
5400 status = SIPE_STATUS_ID_BUSY;
5401 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
5402 } else if (avail < 12000) {
5403 status = SIPE_STATUS_ID_DND;
5404 } else if (avail < 15000) {
5405 status = SIPE_STATUS_ID_BRB;
5406 } else if (avail < 18000) {
5407 status = SIPE_STATUS_ID_AWAY;
5408 } else {
5409 status = SIPE_STATUS_ID_OFFLINE;
5412 if (activity_desc && act) {
5413 g_free(*activity_desc);
5414 *activity_desc = g_strdup(act);
5417 return status;
5421 * Returns 2007-style availability value
5423 * @param sipe_status_id (in)
5424 * @param activity_token (out) Must be g_free()'d after use if consumed.
5426 static int
5427 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
5429 int availability;
5430 sipe_activity activity = SIPE_ACTIVITY_UNSET;
5432 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
5433 availability = 15500;
5434 if (!activity_token || !(*activity_token)) {
5435 activity = SIPE_ACTIVITY_AWAY;
5437 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
5438 availability = 12500;
5439 activity = SIPE_ACTIVITY_BRB;
5440 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
5441 availability = 9500;
5442 activity = SIPE_ACTIVITY_DND;
5443 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
5444 availability = 6500;
5445 if (!activity_token || !(*activity_token)) {
5446 activity = SIPE_ACTIVITY_BUSY;
5448 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
5449 availability = 3500;
5450 activity = SIPE_ACTIVITY_ONLINE;
5451 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
5452 availability = 0;
5453 } else {
5454 // Offline or invisible
5455 availability = 18500;
5456 activity = SIPE_ACTIVITY_OFFLINE;
5459 if (activity_token) {
5460 *activity_token = g_strdup(sipe_activity_map[activity].token);
5462 return availability;
5465 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
5466 const gchar *data, unsigned len)
5468 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5469 const char *uri;
5470 sipe_xml *xn_categories;
5471 const sipe_xml *xn_category;
5472 const char *status = NULL;
5473 gboolean do_update_status = FALSE;
5474 gboolean has_note_cleaned = FALSE;
5475 gboolean has_free_busy_cleaned = FALSE;
5477 xn_categories = sipe_xml_parse(data, len);
5478 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
5480 for (xn_category = sipe_xml_child(xn_categories, "category");
5481 xn_category ;
5482 xn_category = sipe_xml_twin(xn_category) )
5484 const sipe_xml *xn_node;
5485 const char *tmp;
5486 const char *attrVar = sipe_xml_attribute(xn_category, "name");
5487 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
5488 sipe_utils_str_to_time(tmp) : 0;
5490 /* contactCard */
5491 if (sipe_strequal(attrVar, "contactCard"))
5493 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
5495 if (card) {
5496 const sipe_xml *node;
5497 /* identity - Display Name and email */
5498 node = sipe_xml_child(card, "identity");
5499 if (node) {
5500 char* display_name = sipe_xml_data(
5501 sipe_xml_child(node, "name/displayName"));
5502 char* email = sipe_xml_data(
5503 sipe_xml_child(node, "email"));
5505 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
5506 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
5508 g_free(display_name);
5509 g_free(email);
5511 /* company */
5512 node = sipe_xml_child(card, "company");
5513 if (node) {
5514 char* company = sipe_xml_data(node);
5515 sipe_update_user_info(sipe_private, uri, COMPANY_PROP, company);
5516 g_free(company);
5518 /* department */
5519 node = sipe_xml_child(card, "department");
5520 if (node) {
5521 char* department = sipe_xml_data(node);
5522 sipe_update_user_info(sipe_private, uri, DEPARTMENT_PROP, department);
5523 g_free(department);
5525 /* title */
5526 node = sipe_xml_child(card, "title");
5527 if (node) {
5528 char* title = sipe_xml_data(node);
5529 sipe_update_user_info(sipe_private, uri, TITLE_PROP, title);
5530 g_free(title);
5532 /* office */
5533 node = sipe_xml_child(card, "office");
5534 if (node) {
5535 char* office = sipe_xml_data(node);
5536 sipe_update_user_info(sipe_private, uri, OFFICE_PROP, office);
5537 g_free(office);
5539 /* site (url) */
5540 node = sipe_xml_child(card, "url");
5541 if (node) {
5542 char* site = sipe_xml_data(node);
5543 sipe_update_user_info(sipe_private, uri, SITE_PROP, site);
5544 g_free(site);
5546 /* phone */
5547 for (node = sipe_xml_child(card, "phone");
5548 node;
5549 node = sipe_xml_twin(node))
5551 const char *phone_type = sipe_xml_attribute(node, "type");
5552 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
5553 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
5555 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
5557 g_free(phone);
5558 g_free(phone_display_string);
5560 /* address */
5561 for (node = sipe_xml_child(card, "address");
5562 node;
5563 node = sipe_xml_twin(node))
5565 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
5566 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
5567 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
5568 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
5569 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
5570 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
5572 sipe_update_user_info(sipe_private, uri, ADDRESS_STREET_PROP, street);
5573 sipe_update_user_info(sipe_private, uri, ADDRESS_CITY_PROP, city);
5574 sipe_update_user_info(sipe_private, uri, ADDRESS_STATE_PROP, state);
5575 sipe_update_user_info(sipe_private, uri, ADDRESS_ZIPCODE_PROP, zipcode);
5576 sipe_update_user_info(sipe_private, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
5578 g_free(street);
5579 g_free(city);
5580 g_free(state);
5581 g_free(zipcode);
5582 g_free(country_code);
5584 break;
5589 /* note */
5590 else if (sipe_strequal(attrVar, "note"))
5592 if (uri) {
5593 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
5595 if (!has_note_cleaned) {
5596 has_note_cleaned = TRUE;
5598 g_free(sbuddy->note);
5599 sbuddy->note = NULL;
5600 sbuddy->is_oof_note = FALSE;
5601 sbuddy->note_since = publish_time;
5603 do_update_status = TRUE;
5605 if (sbuddy && (publish_time >= sbuddy->note_since)) {
5606 /* clean up in case no 'note' element is supplied
5607 * which indicate note removal in client
5609 g_free(sbuddy->note);
5610 sbuddy->note = NULL;
5611 sbuddy->is_oof_note = FALSE;
5612 sbuddy->note_since = publish_time;
5614 xn_node = sipe_xml_child(xn_category, "note/body");
5615 if (xn_node) {
5616 char *tmp;
5617 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
5618 g_free(tmp);
5619 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
5620 sbuddy->note_since = publish_time;
5622 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
5623 uri, sbuddy->note ? sbuddy->note : "");
5625 /* to trigger UI refresh in case no status info is supplied in this update */
5626 do_update_status = TRUE;
5630 /* state */
5631 else if(sipe_strequal(attrVar, "state"))
5633 char *tmp;
5634 int availability;
5635 const sipe_xml *xn_availability;
5636 const sipe_xml *xn_activity;
5637 const sipe_xml *xn_meeting_subject;
5638 const sipe_xml *xn_meeting_location;
5639 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
5641 xn_node = sipe_xml_child(xn_category, "state");
5642 if (!xn_node) continue;
5643 xn_availability = sipe_xml_child(xn_node, "availability");
5644 if (!xn_availability) continue;
5645 xn_activity = sipe_xml_child(xn_node, "activity");
5646 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
5647 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
5649 tmp = sipe_xml_data(xn_availability);
5650 availability = atoi(tmp);
5651 g_free(tmp);
5653 /* activity, meeting_subject, meeting_location */
5654 if (sbuddy) {
5655 char *tmp = NULL;
5657 /* activity */
5658 g_free(sbuddy->activity);
5659 sbuddy->activity = NULL;
5660 if (xn_activity) {
5661 const char *token = sipe_xml_attribute(xn_activity, "token");
5662 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
5664 /* from token */
5665 if (!is_empty(token)) {
5666 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
5668 /* from custom element */
5669 if (xn_custom) {
5670 char *custom = sipe_xml_data(xn_custom);
5672 if (!is_empty(custom)) {
5673 sbuddy->activity = custom;
5674 custom = NULL;
5676 g_free(custom);
5679 /* meeting_subject */
5680 g_free(sbuddy->meeting_subject);
5681 sbuddy->meeting_subject = NULL;
5682 if (xn_meeting_subject) {
5683 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
5685 if (!is_empty(meeting_subject)) {
5686 sbuddy->meeting_subject = meeting_subject;
5687 meeting_subject = NULL;
5689 g_free(meeting_subject);
5691 /* meeting_location */
5692 g_free(sbuddy->meeting_location);
5693 sbuddy->meeting_location = NULL;
5694 if (xn_meeting_location) {
5695 char *meeting_location = sipe_xml_data(xn_meeting_location);
5697 if (!is_empty(meeting_location)) {
5698 sbuddy->meeting_location = meeting_location;
5699 meeting_location = NULL;
5701 g_free(meeting_location);
5704 status = sipe_get_status_by_availability(availability, &tmp);
5705 if (sbuddy->activity && tmp) {
5706 char *tmp2 = sbuddy->activity;
5708 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
5709 g_free(tmp);
5710 g_free(tmp2);
5711 } else if (tmp) {
5712 sbuddy->activity = tmp;
5716 do_update_status = TRUE;
5718 /* calendarData */
5719 else if(sipe_strequal(attrVar, "calendarData"))
5721 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
5722 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
5723 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
5725 if (sbuddy && xn_free_busy) {
5726 if (!has_free_busy_cleaned) {
5727 has_free_busy_cleaned = TRUE;
5729 g_free(sbuddy->cal_start_time);
5730 sbuddy->cal_start_time = NULL;
5732 g_free(sbuddy->cal_free_busy_base64);
5733 sbuddy->cal_free_busy_base64 = NULL;
5735 g_free(sbuddy->cal_free_busy);
5736 sbuddy->cal_free_busy = NULL;
5738 sbuddy->cal_free_busy_published = publish_time;
5741 if (publish_time >= sbuddy->cal_free_busy_published) {
5742 g_free(sbuddy->cal_start_time);
5743 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
5745 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
5746 15 : 0;
5748 g_free(sbuddy->cal_free_busy_base64);
5749 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
5751 g_free(sbuddy->cal_free_busy);
5752 sbuddy->cal_free_busy = NULL;
5754 sbuddy->cal_free_busy_published = publish_time;
5756 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);
5760 if (sbuddy && xn_working_hours) {
5761 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
5766 if (do_update_status) {
5767 if (!status) { /* no status category in this update, using contact's current status */
5768 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5769 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
5770 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
5771 status = purple_status_get_id(pstatus);
5774 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
5775 sipe_got_user_status(sipe_private, uri, status);
5778 sipe_xml_free(xn_categories);
5781 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
5782 GSList *server,
5783 struct sipe_core_private *sipe_private)
5785 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5786 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
5787 payload->host = g_strdup(host);
5788 payload->buddies = server;
5789 sipe_subscribe_presence_batched_routed(sipe_private,
5790 payload);
5791 sipe_subscribe_presence_batched_routed_free(payload);
5794 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
5795 const gchar *data, unsigned len)
5797 sipe_xml *xn_list;
5798 const sipe_xml *xn_resource;
5799 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
5800 g_free, NULL);
5801 GSList *server;
5802 gchar *host;
5804 xn_list = sipe_xml_parse(data, len);
5806 for (xn_resource = sipe_xml_child(xn_list, "resource");
5807 xn_resource;
5808 xn_resource = sipe_xml_twin(xn_resource) )
5810 const char *uri, *state;
5811 const sipe_xml *xn_instance;
5813 xn_instance = sipe_xml_child(xn_resource, "instance");
5814 if (!xn_instance) continue;
5816 uri = sipe_xml_attribute(xn_resource, "uri");
5817 state = sipe_xml_attribute(xn_instance, "state");
5818 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
5820 if (strstr(state, "resubscribe")) {
5821 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
5823 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5824 gchar *user = g_strdup(uri);
5825 host = g_strdup(poolFqdn);
5826 server = g_hash_table_lookup(servers, host);
5827 server = g_slist_append(server, user);
5828 g_hash_table_insert(servers, host, server);
5829 } else {
5830 sipe_subscribe_presence_single(sipe_private,
5831 (void *) uri);
5836 /* Send out any deferred poolFqdn subscriptions */
5837 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
5838 g_hash_table_destroy(servers);
5840 sipe_xml_free(xn_list);
5843 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
5844 const gchar *data, unsigned len)
5846 gchar *uri;
5847 gchar *getbasic;
5848 gchar *activity = NULL;
5849 sipe_xml *pidf;
5850 const sipe_xml *basicstatus = NULL, *tuple, *status;
5851 gboolean isonline = FALSE;
5852 const sipe_xml *display_name_node;
5854 pidf = sipe_xml_parse(data, len);
5855 if (!pidf) {
5856 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
5857 return;
5860 if ((tuple = sipe_xml_child(pidf, "tuple")))
5862 if ((status = sipe_xml_child(tuple, "status"))) {
5863 basicstatus = sipe_xml_child(status, "basic");
5867 if (!basicstatus) {
5868 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
5869 sipe_xml_free(pidf);
5870 return;
5873 getbasic = sipe_xml_data(basicstatus);
5874 if (!getbasic) {
5875 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
5876 sipe_xml_free(pidf);
5877 return;
5880 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
5881 if (strstr(getbasic, "open")) {
5882 isonline = TRUE;
5884 g_free(getbasic);
5886 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5888 display_name_node = sipe_xml_child(pidf, "display-name");
5889 if (display_name_node) {
5890 char * display_name = sipe_xml_data(display_name_node);
5892 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
5893 g_free(display_name);
5896 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
5897 if ((status = sipe_xml_child(tuple, "status"))) {
5898 if ((basicstatus = sipe_xml_child(status, "activities"))) {
5899 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
5900 activity = sipe_xml_data(basicstatus);
5901 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
5907 if (isonline) {
5908 const gchar * status_id = NULL;
5909 if (activity) {
5910 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
5911 status_id = SIPE_STATUS_ID_BUSY;
5912 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
5913 status_id = SIPE_STATUS_ID_AWAY;
5917 if (!status_id) {
5918 status_id = SIPE_STATUS_ID_AVAILABLE;
5921 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
5922 sipe_got_user_status(sipe_private, uri, status_id);
5923 } else {
5924 sipe_got_user_status(sipe_private, uri, SIPE_STATUS_ID_OFFLINE);
5927 g_free(activity);
5928 g_free(uri);
5929 sipe_xml_free(pidf);
5932 /** 2005 */
5933 static void
5934 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
5935 const sipe_xml *xn_userinfo)
5937 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5938 const sipe_xml *xn_states;
5940 g_free(sip->user_states);
5941 sip->user_states = NULL;
5942 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
5943 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
5945 /* this is a hack-around to remove added newline after inner element,
5946 * state in this case, where it shouldn't be.
5947 * After several use of sipe_xml_stringify, amount of added newlines
5948 * grows significantly.
5950 if (orig) {
5951 gchar c, *stripped = orig;
5952 while ((c = *orig++)) {
5953 if ((c != '\n') /* && (c != '\r') */) {
5954 *stripped++ = c;
5957 *stripped = '\0';
5961 /* Publish initial state if not yet.
5962 * Assuming this happens on initial responce to self subscription
5963 * so we've already updated our UserInfo.
5965 if (!sip->initial_state_published) {
5966 send_presence_soap(sipe_private, FALSE);
5967 /* dalayed run */
5968 sipe_schedule_seconds(sipe_private,
5969 "<+update-calendar>",
5970 NULL,
5971 UPDATE_CALENDAR_DELAY,
5972 (sipe_schedule_action) sipe_core_update_calendar,
5973 NULL);
5977 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
5978 const gchar *data, unsigned len)
5980 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5981 char *activity = NULL;
5982 const char *epid;
5983 const char *status_id = NULL;
5984 const char *name;
5985 char *uri;
5986 char *self_uri = sip_uri_self(sipe_private);
5987 int avl;
5988 int act;
5989 const char *device_name = NULL;
5990 const char *cal_start_time = NULL;
5991 const char *cal_granularity = NULL;
5992 char *cal_free_busy_base64 = NULL;
5993 struct sipe_buddy *sbuddy;
5994 const sipe_xml *node;
5995 sipe_xml *xn_presentity;
5996 const sipe_xml *xn_availability;
5997 const sipe_xml *xn_activity;
5998 const sipe_xml *xn_display_name;
5999 const sipe_xml *xn_email;
6000 const sipe_xml *xn_phone_number;
6001 const sipe_xml *xn_userinfo;
6002 const sipe_xml *xn_note;
6003 const sipe_xml *xn_oof;
6004 const sipe_xml *xn_state;
6005 const sipe_xml *xn_contact;
6006 char *note;
6007 char *free_activity;
6008 int user_avail;
6009 const char *user_avail_nil;
6010 int res_avail;
6011 time_t user_avail_since = 0;
6012 time_t activity_since = 0;
6014 /* fix for Reuters environment on Linux */
6015 if (data && strstr(data, "encoding=\"utf-16\"")) {
6016 char *tmp_data;
6017 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6018 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
6019 g_free(tmp_data);
6020 } else {
6021 xn_presentity = sipe_xml_parse(data, len);
6024 xn_availability = sipe_xml_child(xn_presentity, "availability");
6025 xn_activity = sipe_xml_child(xn_presentity, "activity");
6026 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
6027 xn_email = sipe_xml_child(xn_presentity, "email");
6028 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
6029 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
6030 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
6031 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
6032 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
6033 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
6034 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
6035 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
6036 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
6037 note = xn_note ? sipe_xml_data(xn_note) : NULL;
6039 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
6040 user_avail = 0;
6041 user_avail_since = 0;
6044 free_activity = NULL;
6046 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
6047 uri = sip_uri_from_name(name);
6048 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
6049 epid = sipe_xml_attribute(xn_availability, "epid");
6050 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
6052 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
6053 res_avail = sipe_get_availability_by_status(status_id, NULL);
6054 if (user_avail > res_avail) {
6055 res_avail = user_avail;
6056 status_id = sipe_get_status_by_availability(user_avail, NULL);
6059 if (xn_display_name) {
6060 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
6061 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
6062 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
6063 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
6064 char *tel_uri = sip_to_tel_uri(phone_number);
6066 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
6067 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
6068 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
6069 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
6071 g_free(tel_uri);
6072 g_free(phone_label);
6073 g_free(phone_number);
6074 g_free(email);
6075 g_free(display_name);
6078 if (xn_contact) {
6079 /* tel */
6080 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
6082 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6083 const char *phone_type = sipe_xml_attribute(node, "type");
6084 char* phone = sipe_xml_data(node);
6086 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
6088 g_free(phone);
6092 /* devicePresence */
6093 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
6094 const sipe_xml *xn_device_name;
6095 const sipe_xml *xn_calendar_info;
6096 const sipe_xml *xn_state;
6097 char *state;
6099 /* deviceName */
6100 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
6101 xn_device_name = sipe_xml_child(node, "deviceName");
6102 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
6105 /* calendarInfo */
6106 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
6107 if (xn_calendar_info) {
6108 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
6110 if (cal_start_time) {
6111 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
6112 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
6114 if (cal_start_time_t_tmp > cal_start_time_t) {
6115 cal_start_time = cal_start_time_tmp;
6116 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6117 g_free(cal_free_busy_base64);
6118 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6120 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);
6122 } else {
6123 cal_start_time = cal_start_time_tmp;
6124 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
6125 g_free(cal_free_busy_base64);
6126 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
6128 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);
6132 /* state */
6133 xn_state = sipe_xml_child(node, "states/state");
6134 if (xn_state) {
6135 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
6136 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
6138 state = sipe_xml_data(xn_state);
6139 if (dev_avail_since > user_avail_since &&
6140 dev_avail >= res_avail)
6142 res_avail = dev_avail;
6143 if (!is_empty(state))
6145 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
6146 g_free(activity);
6147 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
6148 } else if (sipe_strequal(state, "presenting")) {
6149 g_free(activity);
6150 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
6151 } else {
6152 activity = state;
6153 state = NULL;
6155 activity_since = dev_avail_since;
6157 status_id = sipe_get_status_by_availability(res_avail, &activity);
6159 g_free(state);
6163 /* oof */
6164 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
6165 g_free(activity);
6166 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
6167 activity_since = 0;
6170 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6171 if (sbuddy)
6173 g_free(sbuddy->activity);
6174 sbuddy->activity = activity;
6175 activity = NULL;
6177 sbuddy->activity_since = activity_since;
6179 sbuddy->user_avail = user_avail;
6180 sbuddy->user_avail_since = user_avail_since;
6182 g_free(sbuddy->note);
6183 sbuddy->note = NULL;
6184 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
6186 sbuddy->is_oof_note = (xn_oof != NULL);
6188 g_free(sbuddy->device_name);
6189 sbuddy->device_name = NULL;
6190 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
6192 if (!is_empty(cal_free_busy_base64)) {
6193 g_free(sbuddy->cal_start_time);
6194 sbuddy->cal_start_time = g_strdup(cal_start_time);
6196 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
6198 g_free(sbuddy->cal_free_busy_base64);
6199 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
6200 cal_free_busy_base64 = NULL;
6202 g_free(sbuddy->cal_free_busy);
6203 sbuddy->cal_free_busy = NULL;
6206 sbuddy->last_non_cal_status_id = status_id;
6207 g_free(sbuddy->last_non_cal_activity);
6208 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
6210 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
6211 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
6213 sip->is_oof_note = sbuddy->is_oof_note;
6215 g_free(sip->note);
6216 sip->note = g_strdup(sbuddy->note);
6218 sip->note_since = time(NULL);
6221 g_free(sip->status);
6222 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
6225 g_free(cal_free_busy_base64);
6226 g_free(activity);
6228 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
6229 sipe_got_user_status(sipe_private, uri, status_id);
6231 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
6232 sipe_user_info_has_updated(sipe_private, xn_userinfo);
6235 g_free(note);
6236 sipe_xml_free(xn_presentity);
6237 g_free(uri);
6238 g_free(self_uri);
6241 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
6242 const GSList *fields,
6243 const gchar *body,
6244 gsize length)
6246 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
6248 if (strstr(type,"application/rlmi+xml")) {
6249 process_incoming_notify_rlmi_resub(user_data, body, length);
6250 } else if (strstr(type, "text/xml+msrtc.pidf")) {
6251 process_incoming_notify_msrtc(user_data, body, length);
6252 } else {
6253 process_incoming_notify_rlmi(user_data, body, length);
6257 static void sipe_process_presence(struct sipe_core_private *sipe_private,
6258 struct sipmsg *msg)
6260 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6262 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
6264 if (ctype &&
6265 (strstr(ctype, "application/rlmi+xml") ||
6266 strstr(ctype, "application/msrtc-event-categories+xml")))
6268 if (strstr(ctype, "multipart"))
6270 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
6272 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
6274 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
6276 else if(strstr(ctype, "application/rlmi+xml"))
6278 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
6281 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
6283 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
6285 else
6287 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
6291 static void sipe_presence_timeout_mime_cb(gpointer user_data,
6292 SIPE_UNUSED_PARAMETER const GSList *fields,
6293 const gchar *body,
6294 gsize length)
6296 GSList **buddies = user_data;
6297 sipe_xml *xml = sipe_xml_parse(body, length);
6299 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
6300 const gchar *uri = sipe_xml_attribute(xml, "uri");
6301 const sipe_xml *xn_category;
6304 * automaton: presence is never expected to change
6306 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
6308 for (xn_category = sipe_xml_child(xml, "category");
6309 xn_category;
6310 xn_category = sipe_xml_twin(xn_category)) {
6311 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
6312 "contactCard")) {
6313 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
6314 if (node) {
6315 char *boolean = sipe_xml_data(node);
6316 if (sipe_strequal(boolean, "true")) {
6317 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
6318 uri);
6319 uri = NULL;
6321 g_free(boolean);
6323 break;
6327 if (uri) {
6328 *buddies = g_slist_append(*buddies, sip_uri(uri));
6332 sipe_xml_free(xml);
6335 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
6336 struct sipmsg *msg, gchar *who,
6337 int timeout)
6339 const char *ctype = sipmsg_find_header(msg, "Content-Type");
6340 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6342 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
6344 if (ctype &&
6345 strstr(ctype, "multipart") &&
6346 (strstr(ctype, "application/rlmi+xml") ||
6347 strstr(ctype, "application/msrtc-event-categories+xml"))) {
6348 GSList *buddies = NULL;
6350 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
6352 if (buddies) {
6353 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
6354 payload->host = g_strdup(who);
6355 payload->buddies = buddies;
6356 sipe_schedule_seconds(sipe_private,
6357 action_name,
6358 payload,
6359 timeout,
6360 sipe_subscribe_presence_batched_routed,
6361 sipe_subscribe_presence_batched_routed_free);
6362 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
6365 } else {
6366 sipe_schedule_seconds(sipe_private,
6367 action_name,
6368 g_strdup(who),
6369 timeout,
6370 sipe_subscribe_presence_single,
6371 g_free);
6372 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
6374 g_free(action_name);
6378 * Dispatcher for all incoming subscription information
6379 * whether it comes from NOTIFY, BENOTIFY requests or
6380 * piggy-backed to subscription's OK responce.
6382 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6383 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6385 static void process_incoming_notify(struct sipe_core_private *sipe_private,
6386 struct sipmsg *msg,
6387 gboolean request, gboolean benotify)
6389 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6390 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
6391 const gchar *event = sipmsg_find_header(msg, "Event");
6392 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
6393 char *tmp;
6395 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
6396 event ? event : "",
6397 tmp = fix_newlines(msg->body));
6398 g_free(tmp);
6399 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
6401 /* implicit subscriptions */
6402 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
6403 sipe_process_imdn(sipe_private, msg);
6406 if (event) {
6407 /* for one off subscriptions (send with Expire: 0) */
6408 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
6410 sipe_process_provisioning_v2(sipe_private, msg);
6412 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
6414 sipe_process_provisioning(sipe_private, msg);
6416 else if (sipe_strcase_equal(event, "presence"))
6418 sipe_process_presence(sipe_private, msg);
6420 else if (sipe_strcase_equal(event, "registration-notify"))
6422 sipe_process_registration_notify(sipe_private, msg);
6425 if (!subscription_state || strstr(subscription_state, "active"))
6427 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
6429 sipe_process_roaming_contacts(sipe_private, msg);
6431 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
6433 sipe_process_roaming_self(sipe_private, msg);
6435 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
6437 sipe_process_roaming_acl(sipe_private, msg);
6439 else if (sipe_strcase_equal(event, "presence.wpending"))
6441 sipe_process_presence_wpending(sipe_private, msg);
6443 else if (sipe_strcase_equal(event, "conference"))
6445 sipe_process_conference(sipe_private, msg);
6450 /* The server sends status 'terminated' */
6451 if (subscription_state && strstr(subscription_state, "terminated") ) {
6452 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
6453 gchar *key = sipe_get_subscription_key(event, who);
6455 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
6456 g_free(who);
6458 if (g_hash_table_lookup(sip->subscriptions, key)) {
6459 g_hash_table_remove(sip->subscriptions, key);
6460 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key);
6463 g_free(key);
6466 if (!request && event) {
6467 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
6468 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
6469 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
6471 if (timeout) {
6472 /* 2 min ahead of expiration */
6473 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
6475 if (sipe_strcase_equal(event, "presence.wpending") &&
6476 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
6478 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
6479 sipe_schedule_seconds(sipe_private,
6480 action_name,
6481 NULL,
6482 timeout,
6483 sipe_subscribe_presence_wpending,
6484 NULL);
6485 g_free(action_name);
6487 else if (sipe_strcase_equal(event, "presence") &&
6488 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
6490 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
6491 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
6493 if (sip->batched_support) {
6494 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
6496 else {
6497 sipe_schedule_seconds(sipe_private,
6498 action_name,
6499 g_strdup(who),
6500 timeout,
6501 sipe_subscribe_presence_single,
6502 g_free);
6503 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
6505 g_free(action_name);
6506 g_free(who);
6511 /* The client responses on received a NOTIFY message */
6512 if (request && !benotify)
6514 send_sip_response(sipe_private, msg, 200, "OK", NULL);
6519 * Whether user manually changed status or
6520 * it was changed automatically due to user
6521 * became inactive/active again
6523 static gboolean
6524 sipe_is_user_state(struct sipe_core_private *sipe_private)
6526 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6527 gboolean res;
6528 time_t now = time(NULL);
6530 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
6531 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
6533 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
6535 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
6536 return res;
6539 static void
6540 send_presence_soap0(struct sipe_core_private *sipe_private,
6541 gboolean do_publish_calendar,
6542 gboolean do_reset_status)
6544 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6545 struct sipe_calendar* cal = sip->cal;
6546 int availability = 0;
6547 int activity = 0;
6548 gchar *body;
6549 gchar *tmp;
6550 gchar *tmp2 = NULL;
6551 gchar *res_note = NULL;
6552 gchar *res_oof = NULL;
6553 const gchar *note_pub = NULL;
6554 gchar *states = NULL;
6555 gchar *calendar_data = NULL;
6556 gchar *epid = get_epid(sipe_private);
6557 time_t now = time(NULL);
6558 gchar *since_time_str = sipe_utils_time_to_str(now);
6559 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
6560 const char *user_input;
6561 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
6563 if (oof_note && sip->note) {
6564 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
6565 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
6568 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
6570 if (!sip->initial_state_published ||
6571 do_reset_status)
6573 g_free(sip->status);
6574 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
6577 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
6579 /* Note */
6580 if (pub_oof) {
6581 note_pub = oof_note;
6582 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
6583 cal->published = TRUE;
6584 } else if (sip->note) {
6585 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
6586 g_free(sip->note);
6587 sip->note = NULL;
6588 sip->is_oof_note = FALSE;
6589 sip->note_since = 0;
6590 } else {
6591 note_pub = sip->note;
6592 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
6596 if (note_pub)
6598 /* to protocol internal plain text format */
6599 tmp = sipe_backend_markup_strip_html(note_pub);
6600 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
6601 g_free(tmp);
6604 /* User State */
6605 if (!do_reset_status) {
6606 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
6608 gchar *activity_token = NULL;
6609 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
6611 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
6612 avail_2007,
6613 since_time_str,
6614 epid,
6615 activity_token);
6616 g_free(activity_token);
6618 else /* preserve existing publication */
6620 if (sip->user_states) {
6621 states = g_strdup(sip->user_states);
6624 } else {
6625 /* do nothing - then User state will be erased */
6627 sip->initial_state_published = TRUE;
6629 /* CalendarInfo */
6630 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
6632 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
6633 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
6634 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
6635 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
6636 fb_start_str,
6637 free_busy_base64);
6638 g_free(fb_start_str);
6639 g_free(free_busy_base64);
6642 user_input = !sipe_is_user_state(sipe_private) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
6644 /* forming resulting XML */
6645 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
6646 sipe_private->username,
6647 availability,
6648 activity,
6649 (tmp = g_ascii_strup(g_get_host_name(), -1)),
6650 res_note ? res_note : "",
6651 res_oof ? res_oof : "",
6652 states ? states : "",
6653 calendar_data ? calendar_data : "",
6654 epid,
6655 since_time_str,
6656 since_time_str,
6657 user_input);
6658 g_free(tmp);
6659 g_free(tmp2);
6660 g_free(res_note);
6661 g_free(states);
6662 g_free(calendar_data);
6664 send_soap_request(sipe_private, body);
6666 g_free(body);
6667 g_free(since_time_str);
6668 g_free(epid);
6671 void
6672 send_presence_soap(struct sipe_core_private *sipe_private,
6673 gboolean do_publish_calendar)
6675 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
6679 static gboolean
6680 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
6681 struct sipmsg *msg,
6682 struct transaction *trans)
6684 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
6686 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
6687 sipe_xml *xml;
6688 const sipe_xml *node;
6689 gchar *fault_code;
6690 GHashTable *faults;
6691 int index_our;
6692 gboolean has_device_publication = FALSE;
6694 xml = sipe_xml_parse(msg->body, msg->bodylen);
6696 /* test if version mismatch fault */
6697 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
6698 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
6699 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
6700 g_free(fault_code);
6701 sipe_xml_free(xml);
6702 return TRUE;
6704 g_free(fault_code);
6706 /* accumulating information about faulty versions */
6707 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
6708 for (node = sipe_xml_child(xml, "details/operation");
6709 node;
6710 node = sipe_xml_twin(node))
6712 const gchar *index = sipe_xml_attribute(node, "index");
6713 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
6715 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
6716 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
6718 sipe_xml_free(xml);
6720 /* here we are parsing own request to figure out what publication
6721 * referensed here only by index went wrong
6723 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
6725 /* publication */
6726 for (node = sipe_xml_child(xml, "publications/publication"),
6727 index_our = 1; /* starts with 1 - our first publication */
6728 node;
6729 node = sipe_xml_twin(node), index_our++)
6731 gchar *idx = g_strdup_printf("%d", index_our);
6732 const gchar *curVersion = g_hash_table_lookup(faults, idx);
6733 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
6734 g_free(idx);
6736 if (sipe_strequal("device", categoryName)) {
6737 has_device_publication = TRUE;
6740 if (curVersion) { /* fault exist on this index */
6741 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6742 const gchar *container = sipe_xml_attribute(node, "container");
6743 const gchar *instance = sipe_xml_attribute(node, "instance");
6744 /* key is <category><instance><container> */
6745 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
6746 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
6748 if (category) {
6749 struct sipe_publication *publication =
6750 g_hash_table_lookup(category, key);
6752 SIPE_DEBUG_INFO("key is %s", key);
6754 if (publication) {
6755 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
6756 key, curVersion, publication->version);
6757 /* updating publication's version to the correct one */
6758 publication->version = atoi(curVersion);
6760 } else {
6761 /* We somehow lost this category from our publications... */
6762 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
6763 publication->category = g_strdup(categoryName);
6764 publication->instance = atoi(instance);
6765 publication->container = atoi(container);
6766 publication->version = atoi(curVersion);
6767 category = g_hash_table_new_full(g_str_hash, g_str_equal,
6768 g_free, (GDestroyNotify)free_publication);
6769 g_hash_table_insert(category, g_strdup(key), publication);
6770 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
6771 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
6773 g_free(key);
6776 sipe_xml_free(xml);
6777 g_hash_table_destroy(faults);
6779 /* rebublishing with right versions */
6780 if (has_device_publication) {
6781 send_publish_category_initial(sipe_private);
6782 } else {
6783 send_presence_status(sipe_private, NULL);
6786 return TRUE;
6790 * Returns 'device' XML part for publication.
6791 * Must be g_free'd after use.
6793 static gchar *
6794 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
6796 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6797 gchar *uri;
6798 gchar *doc;
6799 gchar *epid = get_epid(sipe_private);
6800 gchar *uuid = generateUUIDfromEPID(epid);
6801 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
6802 /* key is <category><instance><container> */
6803 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
6804 struct sipe_publication *publication =
6805 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
6807 g_free(key);
6808 g_free(epid);
6810 uri = sip_uri_self(sipe_private);
6811 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
6812 device_instance,
6813 publication ? publication->version : 0,
6814 uuid,
6815 uri,
6816 "00:00:00+01:00", /* @TODO make timezone real*/
6817 g_get_host_name()
6820 g_free(uri);
6821 g_free(uuid);
6823 return doc;
6827 * A service method - use
6828 * - send_publish_get_category_state_machine and
6829 * - send_publish_get_category_state_user instead.
6830 * Must be g_free'd after use.
6832 static gchar *
6833 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
6834 gboolean is_user_state)
6836 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6837 int availability = sipe_get_availability_by_status(sip->status, NULL);
6838 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
6839 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
6840 /* key is <category><instance><container> */
6841 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6842 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6843 struct sipe_publication *publication_2 =
6844 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6845 struct sipe_publication *publication_3 =
6846 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6848 g_free(key_2);
6849 g_free(key_3);
6851 if (publication_2 && (publication_2->availability == availability))
6853 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
6854 return NULL; /* nothing to update */
6857 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
6858 instance,
6859 publication_2 ? publication_2->version : 0,
6860 availability,
6861 instance,
6862 publication_3 ? publication_3->version : 0,
6863 availability);
6867 * Only Busy and OOF calendar event are published.
6868 * Different instances are used for that.
6870 * Must be g_free'd after use.
6872 static gchar *
6873 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
6874 struct sipe_cal_event *event,
6875 const char *uri,
6876 int cal_satus)
6878 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6879 gchar *start_time_str;
6880 int availability = 0;
6881 gchar *res;
6882 gchar *tmp = NULL;
6883 guint instance = (cal_satus == SIPE_CAL_OOF) ?
6884 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
6885 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
6887 /* key is <category><instance><container> */
6888 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
6889 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
6890 struct sipe_publication *publication_2 =
6891 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
6892 struct sipe_publication *publication_3 =
6893 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
6895 g_free(key_2);
6896 g_free(key_3);
6898 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
6899 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
6900 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
6901 return NULL;
6904 if (event &&
6905 publication_3 &&
6906 (publication_3->availability == availability) &&
6907 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
6909 g_free(tmp);
6910 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
6911 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
6912 return NULL; /* nothing to update */
6914 g_free(tmp);
6916 if (event &&
6917 (event->cal_status == SIPE_CAL_BUSY ||
6918 event->cal_status == SIPE_CAL_OOF))
6920 gchar *availability_xml_str = NULL;
6921 gchar *activity_xml_str = NULL;
6923 if (event->cal_status == SIPE_CAL_BUSY) {
6924 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
6927 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
6928 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6929 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
6930 "minAvailability=\"6500\"",
6931 "maxAvailability=\"8999\"");
6932 } else if (event->cal_status == SIPE_CAL_OOF) {
6933 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
6934 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
6935 "minAvailability=\"12000\"",
6936 "");
6938 start_time_str = sipe_utils_time_to_str(event->start_time);
6940 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
6941 instance,
6942 publication_2 ? publication_2->version : 0,
6943 uri,
6944 start_time_str,
6945 availability_xml_str ? availability_xml_str : "",
6946 activity_xml_str ? activity_xml_str : "",
6947 event->subject ? event->subject : "",
6948 event->location ? event->location : "",
6950 instance,
6951 publication_3 ? publication_3->version : 0,
6952 uri,
6953 start_time_str,
6954 availability_xml_str ? availability_xml_str : "",
6955 activity_xml_str ? activity_xml_str : "",
6956 event->subject ? event->subject : "",
6957 event->location ? event->location : ""
6959 g_free(start_time_str);
6960 g_free(availability_xml_str);
6961 g_free(activity_xml_str);
6964 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6966 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
6967 instance,
6968 publication_2 ? publication_2->version : 0,
6970 instance,
6971 publication_3 ? publication_3->version : 0
6975 return res;
6979 * Returns 'machineState' XML part for publication.
6980 * Must be g_free'd after use.
6982 static gchar *
6983 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
6985 return sipe_publish_get_category_state(sipe_private, FALSE);
6989 * Returns 'userState' XML part for publication.
6990 * Must be g_free'd after use.
6992 static gchar *
6993 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
6995 return sipe_publish_get_category_state(sipe_private, TRUE);
6999 * Returns 'note' XML part for publication.
7000 * Must be g_free'd after use.
7002 * Protocol format for Note is plain text.
7004 * @param note a note in Sipe internal HTML format
7005 * @param note_type either personal or OOF
7007 static gchar *
7008 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
7009 const char *note, /* html */
7010 const char *note_type,
7011 time_t note_start,
7012 time_t note_end)
7014 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7015 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
7016 /* key is <category><instance><container> */
7017 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
7018 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
7019 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
7021 struct sipe_publication *publication_note_200 =
7022 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
7023 struct sipe_publication *publication_note_300 =
7024 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
7025 struct sipe_publication *publication_note_400 =
7026 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
7028 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
7029 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
7030 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
7031 char *res, *tmp1, *tmp2, *tmp3;
7032 char *start_time_attr;
7033 char *end_time_attr;
7035 g_free(tmp);
7036 tmp = NULL;
7037 g_free(key_note_200);
7038 g_free(key_note_300);
7039 g_free(key_note_400);
7041 /* we even need to republish empty note */
7042 if (sipe_strequal(n1, n2))
7044 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7045 g_free(n1);
7046 return NULL; /* nothing to update */
7049 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
7050 g_free(tmp);
7051 tmp = NULL;
7052 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
7053 g_free(tmp);
7055 if (n1) {
7056 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7057 instance,
7058 200,
7059 publication_note_200 ? publication_note_200->version : 0,
7060 note_type,
7061 start_time_attr ? start_time_attr : "",
7062 end_time_attr ? end_time_attr : "",
7063 n1);
7065 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7066 instance,
7067 300,
7068 publication_note_300 ? publication_note_300->version : 0,
7069 note_type,
7070 start_time_attr ? start_time_attr : "",
7071 end_time_attr ? end_time_attr : "",
7072 n1);
7074 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
7075 instance,
7076 400,
7077 publication_note_400 ? publication_note_400->version : 0,
7078 note_type,
7079 start_time_attr ? start_time_attr : "",
7080 end_time_attr ? end_time_attr : "",
7081 n1);
7082 } else {
7083 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7084 "note",
7085 instance,
7086 200,
7087 publication_note_200 ? publication_note_200->version : 0,
7088 "static");
7089 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7090 "note",
7091 instance,
7092 300,
7093 publication_note_200 ? publication_note_200->version : 0,
7094 "static");
7095 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
7096 "note",
7097 instance,
7098 400,
7099 publication_note_200 ? publication_note_200->version : 0,
7100 "static");
7102 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
7104 g_free(start_time_attr);
7105 g_free(end_time_attr);
7106 g_free(tmp1);
7107 g_free(tmp2);
7108 g_free(tmp3);
7109 g_free(n1);
7111 return res;
7115 * Returns 'calendarData' XML part with WorkingHours for publication.
7116 * Must be g_free'd after use.
7118 static gchar *
7119 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
7121 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7122 struct sipe_calendar* cal = sip->cal;
7124 /* key is <category><instance><container> */
7125 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7126 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7127 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7128 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7129 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7130 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7132 struct sipe_publication *publication_cal_1 =
7133 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7134 struct sipe_publication *publication_cal_100 =
7135 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7136 struct sipe_publication *publication_cal_200 =
7137 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7138 struct sipe_publication *publication_cal_300 =
7139 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7140 struct sipe_publication *publication_cal_400 =
7141 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7142 struct sipe_publication *publication_cal_32000 =
7143 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7145 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
7146 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
7148 g_free(key_cal_1);
7149 g_free(key_cal_100);
7150 g_free(key_cal_200);
7151 g_free(key_cal_300);
7152 g_free(key_cal_400);
7153 g_free(key_cal_32000);
7155 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
7156 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7157 return NULL;
7160 if (sipe_strequal(n1, n2))
7162 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7163 return NULL; /* nothing to update */
7166 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
7167 /* 1 */
7168 publication_cal_1 ? publication_cal_1->version : 0,
7169 cal->email,
7170 cal->working_hours_xml_str,
7171 /* 100 - Public */
7172 publication_cal_100 ? publication_cal_100->version : 0,
7173 /* 200 - Company */
7174 publication_cal_200 ? publication_cal_200->version : 0,
7175 cal->email,
7176 cal->working_hours_xml_str,
7177 /* 300 - Team */
7178 publication_cal_300 ? publication_cal_300->version : 0,
7179 cal->email,
7180 cal->working_hours_xml_str,
7181 /* 400 - Personal */
7182 publication_cal_400 ? publication_cal_400->version : 0,
7183 cal->email,
7184 cal->working_hours_xml_str,
7185 /* 32000 - Blocked */
7186 publication_cal_32000 ? publication_cal_32000->version : 0
7191 * Returns 'calendarData' XML part with FreeBusy for publication.
7192 * Must be g_free'd after use.
7194 static gchar *
7195 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
7197 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7198 struct sipe_calendar* cal = sip->cal;
7199 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
7200 char *fb_start_str;
7201 char *free_busy_base64;
7202 const char *st;
7203 const char *fb;
7204 char *res;
7206 /* key is <category><instance><container> */
7207 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
7208 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
7209 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
7210 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
7211 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
7212 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
7214 struct sipe_publication *publication_cal_1 =
7215 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
7216 struct sipe_publication *publication_cal_100 =
7217 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
7218 struct sipe_publication *publication_cal_200 =
7219 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
7220 struct sipe_publication *publication_cal_300 =
7221 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
7222 struct sipe_publication *publication_cal_400 =
7223 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
7224 struct sipe_publication *publication_cal_32000 =
7225 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
7227 g_free(key_cal_1);
7228 g_free(key_cal_100);
7229 g_free(key_cal_200);
7230 g_free(key_cal_300);
7231 g_free(key_cal_400);
7232 g_free(key_cal_32000);
7234 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
7235 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7236 return NULL;
7239 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
7240 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
7242 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
7243 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
7245 /* we will rebuplish the same data to refresh publication time,
7246 * so if data from multiple sources, most recent will be choosen
7248 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7250 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7251 // g_free(fb_start_str);
7252 // g_free(free_busy_base64);
7253 // return NULL; /* nothing to update */
7256 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
7257 /* 1 */
7258 cal_data_instance,
7259 publication_cal_1 ? publication_cal_1->version : 0,
7260 /* 100 - Public */
7261 cal_data_instance,
7262 publication_cal_100 ? publication_cal_100->version : 0,
7263 /* 200 - Company */
7264 cal_data_instance,
7265 publication_cal_200 ? publication_cal_200->version : 0,
7266 cal->email,
7267 fb_start_str,
7268 free_busy_base64,
7269 /* 300 - Team */
7270 cal_data_instance,
7271 publication_cal_300 ? publication_cal_300->version : 0,
7272 cal->email,
7273 fb_start_str,
7274 free_busy_base64,
7275 /* 400 - Personal */
7276 cal_data_instance,
7277 publication_cal_400 ? publication_cal_400->version : 0,
7278 cal->email,
7279 fb_start_str,
7280 free_busy_base64,
7281 /* 32000 - Blocked */
7282 cal_data_instance,
7283 publication_cal_32000 ? publication_cal_32000->version : 0
7286 g_free(fb_start_str);
7287 g_free(free_busy_base64);
7288 return res;
7291 static void send_presence_publish(struct sipe_core_private *sipe_private,
7292 const char *publications)
7294 gchar *uri;
7295 gchar *doc;
7296 gchar *tmp;
7297 gchar *hdr;
7299 uri = sip_uri_self(sipe_private);
7300 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
7301 uri,
7302 publications);
7304 tmp = get_contact(sipe_private);
7305 hdr = g_strdup_printf("Contact: %s\r\n"
7306 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
7308 send_sip_request(sipe_private, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
7310 g_free(tmp);
7311 g_free(hdr);
7312 g_free(uri);
7313 g_free(doc);
7316 static void
7317 send_publish_category_initial(struct sipe_core_private *sipe_private)
7319 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7320 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
7321 gchar *pub_machine;
7322 gchar *publications;
7324 g_free(sip->status);
7325 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
7327 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
7328 publications = g_strdup_printf("%s%s",
7329 pub_device,
7330 pub_machine ? pub_machine : "");
7331 g_free(pub_device);
7332 g_free(pub_machine);
7334 send_presence_publish(sipe_private, publications);
7335 g_free(publications);
7338 static void
7339 send_presence_category_publish(struct sipe_core_private *sipe_private)
7341 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7342 gchar *pub_state = sipe_is_user_state(sipe_private) ?
7343 sipe_publish_get_category_state_user(sipe_private) :
7344 sipe_publish_get_category_state_machine(sipe_private);
7345 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
7346 sip->note,
7347 sip->is_oof_note ? "OOF" : "personal",
7350 gchar *publications;
7352 if (!pub_state && !pub_note) {
7353 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
7354 return;
7357 publications = g_strdup_printf("%s%s",
7358 pub_state ? pub_state : "",
7359 pub_note ? pub_note : "");
7361 g_free(pub_state);
7362 g_free(pub_note);
7364 send_presence_publish(sipe_private, publications);
7365 g_free(publications);
7369 * Publishes self status
7370 * based on own calendar information.
7372 * For 2007+
7374 void
7375 publish_calendar_status_self(struct sipe_core_private *sipe_private,
7376 SIPE_UNUSED_PARAMETER void *unused)
7378 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7379 struct sipe_cal_event* event = NULL;
7380 gchar *pub_cal_working_hours = NULL;
7381 gchar *pub_cal_free_busy = NULL;
7382 gchar *pub_calendar = NULL;
7383 gchar *pub_calendar2 = NULL;
7384 gchar *pub_oof_note = NULL;
7385 const gchar *oof_note;
7386 time_t oof_start = 0;
7387 time_t oof_end = 0;
7389 if (!sip->cal) {
7390 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
7391 return;
7394 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
7395 if (sip->cal->cal_events) {
7396 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
7399 if (!event) {
7400 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
7401 } else {
7402 char *desc = sipe_cal_event_describe(event);
7403 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
7404 g_free(desc);
7407 /* Logic
7408 if OOF
7409 OOF publish, Busy clean
7410 ilse if Busy
7411 OOF clean, Busy publish
7412 else
7413 OOF clean, Busy clean
7415 if (event && event->cal_status == SIPE_CAL_OOF) {
7416 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
7417 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
7418 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
7419 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
7420 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
7421 } else {
7422 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
7423 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
7426 oof_note = sipe_ews_get_oof_note(sip->cal);
7427 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
7428 oof_start = sip->cal->oof_start;
7429 oof_end = sip->cal->oof_end;
7431 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
7433 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
7434 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
7436 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
7437 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
7438 } else {
7439 gchar *publications = g_strdup_printf("%s%s%s%s%s",
7440 pub_cal_working_hours ? pub_cal_working_hours : "",
7441 pub_cal_free_busy ? pub_cal_free_busy : "",
7442 pub_calendar ? pub_calendar : "",
7443 pub_calendar2 ? pub_calendar2 : "",
7444 pub_oof_note ? pub_oof_note : "");
7446 send_presence_publish(sipe_private, publications);
7447 g_free(publications);
7450 g_free(pub_cal_working_hours);
7451 g_free(pub_cal_free_busy);
7452 g_free(pub_calendar);
7453 g_free(pub_calendar2);
7454 g_free(pub_oof_note);
7456 /* repeat scheduling */
7457 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
7460 static void send_presence_status(struct sipe_core_private *sipe_private,
7461 SIPE_UNUSED_PARAMETER void *unused)
7463 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7464 PurpleStatus * status = purple_account_get_active_status(sip->account);
7466 if (!status) return;
7468 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
7469 purple_status_get_id(status) ? purple_status_get_id(status) : "",
7470 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
7472 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
7473 send_presence_category_publish(sipe_private);
7474 } else {
7475 send_presence_soap(sipe_private, FALSE);
7479 void process_input_message(struct sipe_core_private *sipe_private,
7480 struct sipmsg *msg)
7482 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7483 gboolean found = FALSE;
7484 const char *method = msg->method ? msg->method : "NOT FOUND";
7485 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg->response,method);
7486 if (msg->response == 0) { /* request */
7487 if (sipe_strequal(method, "MESSAGE")) {
7488 process_incoming_message(sipe_private, msg);
7489 found = TRUE;
7490 } else if (sipe_strequal(method, "NOTIFY")) {
7491 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
7492 process_incoming_notify(sipe_private, msg, TRUE, FALSE);
7493 found = TRUE;
7494 } else if (sipe_strequal(method, "BENOTIFY")) {
7495 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
7496 process_incoming_notify(sipe_private, msg, TRUE, TRUE);
7497 found = TRUE;
7498 } else if (sipe_strequal(method, "INVITE")) {
7499 process_incoming_invite(sipe_private, msg);
7500 found = TRUE;
7501 } else if (sipe_strequal(method, "REFER")) {
7502 process_incoming_refer(sipe_private, msg);
7503 found = TRUE;
7504 } else if (sipe_strequal(method, "OPTIONS")) {
7505 process_incoming_options(sipe_private, msg);
7506 found = TRUE;
7507 } else if (sipe_strequal(method, "INFO")) {
7508 process_incoming_info(sipe_private, msg);
7509 found = TRUE;
7510 } else if (sipe_strequal(method, "ACK")) {
7511 // ACK's don't need any response
7512 found = TRUE;
7513 } else if (sipe_strequal(method, "PRACK")) {
7514 found = TRUE;
7515 send_sip_response(sipe_private, msg, 200, "OK", NULL);
7516 } else if (sipe_strequal(method, "SUBSCRIBE")) {
7517 // LCS 2005 sends us these - just respond 200 OK
7518 found = TRUE;
7519 send_sip_response(sipe_private, msg, 200, "OK", NULL);
7520 } else if (sipe_strequal(method, "CANCEL")) {
7521 process_incoming_cancel(sipe_private, msg);
7522 found = TRUE;
7523 } else if (sipe_strequal(method, "BYE")) {
7524 process_incoming_bye(sipe_private, msg);
7525 found = TRUE;
7526 } else {
7527 send_sip_response(sipe_private, msg, 501, "Not implemented", NULL);
7529 } else { /* response */
7530 struct transaction *trans = transactions_find(sipe_private,
7531 msg);
7532 if (trans) {
7533 if (msg->response == 407) {
7534 gchar *resend, *auth;
7535 const gchar *ptmp;
7537 if (sip->proxy.retries > 30) return;
7538 sip->proxy.retries++;
7539 /* do proxy authentication */
7541 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
7543 fill_auth(ptmp, &sip->proxy);
7544 auth = auth_header(sipe_private, &sip->proxy, trans->msg);
7545 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
7546 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
7547 g_free(auth);
7548 resend = sipmsg_to_string(trans->msg);
7549 /* resend request */
7550 sipe_backend_transport_message(sipe_private->transport, resend);
7551 g_free(resend);
7552 } else {
7553 if (msg->response < 200) {
7554 if (msg->bodylen != 0) {
7555 SIPE_DEBUG_INFO("got provisional (%d) response with body", msg->response);
7556 if (trans->callback) {
7557 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
7558 (trans->callback)(sipe_private, msg, trans);
7560 } else {
7561 /* ignore provisional response */
7562 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg->response);
7564 } else {
7565 sip->proxy.retries = 0;
7566 if (sipe_strequal(trans->msg->method, "REGISTER")) {
7567 if (msg->response == 401)
7569 sip->registrar.retries++;
7571 else
7573 sip->registrar.retries = 0;
7575 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip->cseq);
7576 } else {
7577 if (msg->response == 401) {
7578 gchar *resend, *auth, *ptmp;
7579 const char* auth_scheme;
7581 if (sip->registrar.retries > 4) return;
7582 sip->registrar.retries++;
7584 auth_scheme = sipe_get_auth_scheme_name(sipe_private);
7585 ptmp = sipmsg_find_auth_header(msg, auth_scheme);
7587 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp ? ptmp : "");
7588 if (!ptmp) {
7589 char *tmp2 = g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme, NULL);
7590 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
7591 SIPE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
7592 tmp2);
7593 g_free(tmp2);
7594 return;
7597 fill_auth(ptmp, &sip->registrar);
7598 auth = auth_header(sipe_private, &sip->registrar, trans->msg);
7599 sipmsg_remove_header_now(trans->msg, "Authorization");
7600 sipmsg_add_header_now_pos(trans->msg, "Authorization", auth, 5);
7601 g_free(auth);
7602 resend = sipmsg_to_string(trans->msg);
7603 /* resend request */
7604 sipe_backend_transport_message(sipe_private->transport, resend);
7605 g_free(resend);
7609 if (trans->callback) {
7610 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
7611 /* call the callback to process response*/
7612 (trans->callback)(sipe_private, msg, trans);
7615 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip->cseq);
7616 transactions_remove(sipe_private, trans);
7620 found = TRUE;
7621 } else {
7622 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
7625 if (!found) {
7626 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method, msg->response);
7630 static guint sipe_ht_hash_nick(const char *nick)
7632 char *lc = g_utf8_strdown(nick, -1);
7633 guint bucket = g_str_hash(lc);
7634 g_free(lc);
7636 return bucket;
7639 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
7641 char *nick1_norm = NULL;
7642 char *nick2_norm = NULL;
7643 gboolean equal;
7645 if (nick1 == NULL && nick2 == NULL) return TRUE;
7646 if (nick1 == NULL || nick2 == NULL ||
7647 !g_utf8_validate(nick1, -1, NULL) ||
7648 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
7650 nick1_norm = g_utf8_casefold(nick1, -1);
7651 nick2_norm = g_utf8_casefold(nick2, -1);
7652 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
7653 g_free(nick2_norm);
7654 g_free(nick1_norm);
7656 return equal;
7659 /* temporary function */
7660 void sipe_purple_setup(struct sipe_core_public *sipe_public,
7661 PurpleConnection *gc)
7663 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
7664 sip->gc = gc;
7665 sip->account = purple_connection_get_account(gc);
7668 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
7669 const gchar *login_domain,
7670 const gchar *login_account,
7671 const gchar *password,
7672 const gchar *email,
7673 const gchar *email_url,
7674 const gchar **errmsg)
7676 struct sipe_core_private *sipe_private;
7677 struct sipe_account_data *sip;
7678 gchar **user_domain;
7680 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
7682 /* ensure that sign-in name doesn't contain invalid characters */
7683 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
7684 *errmsg = _("SIP Exchange user name contains invalid characters");
7685 return NULL;
7688 /* ensure that sign-in name format is name@domain */
7689 if (!strchr(signin_name, '@') ||
7690 g_str_has_prefix(signin_name, "@") ||
7691 g_str_has_suffix(signin_name, "@")) {
7692 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
7693 return NULL;
7696 /* ensure that email format is name@domain (if provided) */
7697 if (!is_empty(email) &&
7698 (!strchr(email, '@') ||
7699 g_str_has_prefix(email, "@") ||
7700 g_str_has_suffix(email, "@")))
7702 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
7703 return NULL;
7706 /* ensure that user name doesn't contain spaces */
7707 user_domain = g_strsplit(signin_name, "@", 2);
7708 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
7709 if (strchr(user_domain[0], ' ') != NULL) {
7710 g_strfreev(user_domain);
7711 *errmsg = _("SIP Exchange user name contains whitespace");
7712 return NULL;
7715 /* ensure that email_url is in proper format if enabled (if provided).
7716 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
7717 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
7719 if (!is_empty(email_url)) {
7720 char *tmp = g_ascii_strdown(email_url, -1);
7721 if (!g_str_has_prefix(tmp, "https://"))
7723 g_free(tmp);
7724 g_strfreev(user_domain);
7725 *errmsg = _("Email services URL should be valid if provided\n"
7726 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
7727 "Example: https://domino.corp.com/maildatabase.nsf");
7728 return NULL;
7730 g_free(tmp);
7733 sipe_private = g_new0(struct sipe_core_private, 1);
7734 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
7735 sip->public = (struct sipe_core_public *)sipe_private;
7736 sip->private = sipe_private;
7737 sip->reregister_set = FALSE;
7738 sip->reauthenticate_set = FALSE;
7739 sip->subscribed = FALSE;
7740 sip->subscribed_buddies = FALSE;
7741 sip->initial_state_published = FALSE;
7742 sipe_private->username = g_strdup(signin_name);
7743 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
7744 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
7745 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
7746 sip->password = g_strdup(password);
7747 sipe_private->public.sip_name = g_strdup(user_domain[0]);
7748 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
7749 g_strfreev(user_domain);
7751 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
7752 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
7753 g_free, (GDestroyNotify)g_hash_table_destroy);
7754 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
7755 g_free, (GDestroyNotify)sipe_subscription_free);
7756 sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL);
7757 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
7759 return((struct sipe_core_public *)sipe_private);
7762 static void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
7764 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7766 g_free(sipe_private->epid);
7767 sipe_private->epid = NULL;
7769 sipe_backend_transport_disconnect(sipe_private->transport);
7770 sipe_private->transport = NULL;
7771 g_free(sipe_private->server_name);
7772 sipe_private->server_name = NULL;
7773 sipe_private->service_data = NULL;
7775 sipe_auth_free(&sip->registrar);
7776 sipe_auth_free(&sip->proxy);
7778 g_free(sipe_private->server_version);
7779 sipe_private->server_version = NULL;
7781 sipe_schedule_cancel_all(sipe_private);
7783 if (sip->allow_events) {
7784 GSList *entry = sip->allow_events;
7785 while (entry) {
7786 g_free(entry->data);
7787 entry = entry->next;
7790 g_slist_free(sip->allow_events);
7792 if (sip->containers) {
7793 GSList *entry = sip->containers;
7794 while (entry) {
7795 free_container((struct sipe_container *)entry->data);
7796 entry = entry->next;
7799 g_slist_free(sip->containers);
7801 if (sipe_private->contact)
7802 g_free(sipe_private->contact);
7803 sipe_private->contact = NULL;
7804 if (sip->regcallid)
7805 g_free(sip->regcallid);
7806 sip->regcallid = NULL;
7808 if (sip->focus_factory_uri)
7809 g_free(sip->focus_factory_uri);
7810 sip->focus_factory_uri = NULL;
7812 sip->processing_input = FALSE;
7814 if (sip->cal) {
7815 sipe_cal_calendar_free(sip->cal);
7817 sip->cal = NULL;
7821 * A callback for g_hash_table_foreach_remove
7823 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
7824 SIPE_UNUSED_PARAMETER gpointer user_data)
7826 sipe_free_buddy((struct sipe_buddy *) buddy);
7828 /* We must return TRUE as the key/value have already been deleted */
7829 return(TRUE);
7832 void sipe_core_deallocate(struct sipe_core_public *sipe_public)
7834 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
7835 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7837 /* leave all conversations */
7838 sipe_session_close_all(sipe_private);
7839 sipe_session_remove_all(sipe_private);
7841 if (sip->csta) {
7842 sip_csta_close(sipe_private);
7845 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
7846 /* unsubscribe all */
7847 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sipe_private);
7849 /* unregister */
7850 do_register_exp(sipe_private, 0);
7853 sipe_connection_cleanup(sipe_private);
7854 g_free(sipe_private->public.sip_name);
7855 g_free(sipe_private->public.sip_domain);
7856 g_free(sipe_private->useragent);
7857 g_free(sipe_private->username);
7858 g_free(sip->email);
7859 g_free(sip->password);
7860 g_free(sip->authdomain);
7861 g_free(sip->authuser);
7862 g_free(sip->status);
7863 g_free(sip->note);
7864 g_free(sip->user_states);
7866 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
7867 g_hash_table_destroy(sipe_private->buddies);
7868 g_hash_table_destroy(sip->our_publications);
7869 g_hash_table_destroy(sip->user_state_publications);
7870 g_hash_table_destroy(sip->subscriptions);
7871 g_hash_table_destroy(sip->filetransfers);
7873 if (sip->groups) {
7874 GSList *entry = sip->groups;
7875 while (entry) {
7876 struct sipe_group *group = entry->data;
7877 g_free(group->name);
7878 g_free(group);
7879 entry = entry->next;
7882 g_slist_free(sip->groups);
7884 if (sip->our_publication_keys) {
7885 GSList *entry = sip->our_publication_keys;
7886 while (entry) {
7887 g_free(entry->data);
7888 entry = entry->next;
7891 g_slist_free(sip->our_publication_keys);
7893 while (sipe_private->transactions)
7894 transactions_remove(sipe_private,
7895 sipe_private->transactions->data);
7896 g_free(sip);
7897 g_free(sipe_private);
7900 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
7901 SIPE_UNUSED_PARAMETER void *user_data)
7903 PurpleAccount *acct = purple_connection_get_account(gc);
7904 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
7905 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
7906 if (conv == NULL)
7907 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
7908 purple_conversation_present(conv);
7909 g_free(id);
7912 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
7913 SIPE_UNUSED_PARAMETER void *user_data)
7916 purple_blist_request_add_buddy(purple_connection_get_account(gc),
7917 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
7920 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
7921 struct sipmsg *msg,
7922 SIPE_UNUSED_PARAMETER struct transaction *trans)
7924 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
7925 PurpleNotifySearchResults *results;
7926 PurpleNotifySearchColumn *column;
7927 sipe_xml *searchResults;
7928 const sipe_xml *mrow;
7929 int match_count = 0;
7930 gboolean more = FALSE;
7931 gchar *secondary;
7933 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
7935 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
7936 if (!searchResults) {
7937 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
7938 return FALSE;
7941 results = purple_notify_searchresults_new();
7943 if (results == NULL) {
7944 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
7945 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
7947 sipe_xml_free(searchResults);
7948 return FALSE;
7951 column = purple_notify_searchresults_column_new(_("User name"));
7952 purple_notify_searchresults_column_add(results, column);
7954 column = purple_notify_searchresults_column_new(_("Name"));
7955 purple_notify_searchresults_column_add(results, column);
7957 column = purple_notify_searchresults_column_new(_("Company"));
7958 purple_notify_searchresults_column_add(results, column);
7960 column = purple_notify_searchresults_column_new(_("Country"));
7961 purple_notify_searchresults_column_add(results, column);
7963 column = purple_notify_searchresults_column_new(_("Email"));
7964 purple_notify_searchresults_column_add(results, column);
7966 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
7967 GList *row = NULL;
7969 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
7970 row = g_list_append(row, g_strdup(uri_parts[1]));
7971 g_strfreev(uri_parts);
7973 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
7974 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
7975 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
7976 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
7978 purple_notify_searchresults_row_add(results, row);
7979 match_count++;
7982 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
7983 char *data = sipe_xml_data(mrow);
7984 more = (g_strcasecmp(data, "true") == 0);
7985 g_free(data);
7988 secondary = g_strdup_printf(
7989 dngettext(PACKAGE_NAME,
7990 "Found %d contact%s:",
7991 "Found %d contacts%s:", match_count),
7992 match_count, more ? _(" (more matched your query)") : "");
7994 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
7995 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
7996 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
7998 g_free(secondary);
7999 sipe_xml_free(searchResults);
8000 return TRUE;
8003 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8005 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
8006 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
8007 unsigned i = 0;
8009 if (!attrs) return;
8011 do {
8012 PurpleRequestField *field = entries->data;
8013 const char *id = purple_request_field_get_id(field);
8014 const char *value = purple_request_field_string_get_value(field);
8016 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
8018 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
8019 } while ((entries = g_list_next(entries)) != NULL);
8020 attrs[i] = NULL;
8022 if (i > 0) {
8023 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
8024 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
8025 gchar *query = g_strjoinv(NULL, attrs);
8026 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
8027 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
8028 send_soap_request_with_cb(sipe_private, domain_uri, body,
8029 process_search_contact_response, NULL);
8030 g_free(domain_uri);
8031 g_free(body);
8032 g_free(query);
8035 g_strfreev(attrs);
8038 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
8039 gpointer value,
8040 GString* str)
8042 struct sipe_publication *publication = value;
8044 g_string_append_printf( str,
8045 SIPE_PUB_XML_PUBLICATION_CLEAR,
8046 publication->category,
8047 publication->instance,
8048 publication->container,
8049 publication->version,
8050 "static");
8053 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
8055 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
8056 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
8057 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
8059 GString* str = g_string_new(NULL);
8060 gchar *publications;
8062 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
8063 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
8064 return;
8067 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
8068 publications = g_string_free(str, FALSE);
8070 send_presence_publish(sipe_private, publications);
8071 g_free(publications);
8073 else /* 2005 */
8075 send_presence_soap0(sipe_private, FALSE, TRUE);
8079 /** for Access levels menu */
8080 #define INDENT_FMT " %s"
8082 /** Member is directly placed to access level container.
8083 * For example SIP URI of user is in the container.
8085 #define INDENT_MARKED_FMT "* %s"
8087 /** Member is indirectly belong to access level container.
8088 * For example 'sameEnterprise' is in the container and user
8089 * belongs to that same enterprise.
8091 #define INDENT_MARKED_INHERITED_FMT "= %s"
8093 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
8094 const gchar *name,
8095 const gchar *status_name,
8096 gboolean is_online)
8098 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
8099 gchar *note = NULL;
8100 gboolean is_oof_note = FALSE;
8101 gchar *activity = NULL;
8102 gchar *calendar = NULL;
8103 gchar *meeting_subject = NULL;
8104 gchar *meeting_location = NULL;
8105 gchar *access_text = NULL;
8106 GSList *info = NULL;
8108 #define SIPE_ADD_BUDDY_INFO(l, t) \
8110 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
8111 sbi->label = (l); \
8112 sbi->text = (t); \
8113 info = g_slist_append(info, sbi); \
8116 if (sipe_public) { //happens on pidgin exit
8117 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
8118 if (sbuddy) {
8119 note = sbuddy->note;
8120 is_oof_note = sbuddy->is_oof_note;
8121 activity = sbuddy->activity;
8122 calendar = sipe_cal_get_description(sbuddy);
8123 meeting_subject = sbuddy->meeting_subject;
8124 meeting_location = sbuddy->meeting_location;
8126 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
8127 gboolean is_group_access = FALSE;
8128 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
8129 const char *access_level = sipe_get_access_level_name(container_id);
8130 access_text = is_group_access ?
8131 g_strdup(access_level) :
8132 g_strdup_printf(INDENT_MARKED_FMT, access_level);
8136 //Layout
8137 if (is_online)
8139 gchar *status_str = g_strdup(activity ? activity : status_name);
8141 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
8143 if (is_online && !is_empty(calendar))
8145 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
8146 calendar = NULL;
8148 g_free(calendar);
8149 if (!is_empty(meeting_location))
8151 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
8153 if (!is_empty(meeting_subject))
8155 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
8157 if (note)
8159 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
8160 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
8161 g_strdup_printf("<i>%s</i>", note));
8163 if (access_text) {
8164 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
8167 return(info);
8170 static PurpleBuddy *
8171 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
8173 PurpleBuddy *clone;
8174 const gchar *server_alias, *email;
8175 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
8177 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
8179 purple_blist_add_buddy(clone, NULL, group, NULL);
8181 server_alias = purple_buddy_get_server_alias(buddy);
8182 if (server_alias) {
8183 purple_blist_server_alias_buddy(clone, server_alias);
8186 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8187 if (email) {
8188 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
8191 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
8192 //for UI to update;
8193 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
8194 return clone;
8197 static void
8198 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
8200 PurpleBuddy *buddy, *b;
8201 PurpleConnection *gc;
8202 PurpleGroup * group = purple_find_group(group_name);
8204 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
8206 buddy = (PurpleBuddy *)node;
8208 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
8209 gc = purple_account_get_connection(buddy->account);
8211 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
8212 if (!b){
8213 purple_blist_add_buddy_clone(group, buddy);
8216 sipe_group_buddy(gc, buddy->name, NULL, group_name);
8219 static void
8220 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
8222 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
8224 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
8226 /* 2007+ conference */
8227 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
8229 sipe_conf_add(sipe_private, buddy->name);
8231 else /* 2005- multiparty chat */
8233 gchar *self = sip_uri_self(sipe_private);
8234 struct sip_session *session;
8236 session = sipe_session_add_chat(sipe_private);
8237 session->chat_title = sipe_chat_get_name(session->callid);
8238 session->roster_manager = g_strdup(self);
8240 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
8241 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
8242 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
8243 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
8245 g_free(self);
8249 static gboolean
8250 sipe_is_election_finished(struct sip_session *session)
8252 gboolean res = TRUE;
8254 SIPE_DIALOG_FOREACH {
8255 if (dialog->election_vote == 0) {
8256 res = FALSE;
8257 break;
8259 } SIPE_DIALOG_FOREACH_END;
8261 if (res) {
8262 session->is_voting_in_progress = FALSE;
8264 return res;
8267 static void
8268 sipe_election_start(struct sipe_core_private *sipe_private,
8269 struct sip_session *session)
8271 if (session->is_voting_in_progress) {
8272 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
8273 return;
8274 } else {
8275 session->is_voting_in_progress = TRUE;
8277 session->bid = rand();
8279 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
8281 SIPE_DIALOG_FOREACH {
8282 /* reset election_vote for each chat participant */
8283 dialog->election_vote = 0;
8285 /* send RequestRM to each chat participant*/
8286 sipe_send_election_request_rm(sipe_private, dialog, session->bid);
8287 } SIPE_DIALOG_FOREACH_END;
8289 sipe_schedule_seconds(sipe_private,
8290 "<+election-result>",
8291 session,
8293 sipe_election_result,
8294 NULL);
8298 * @param who a URI to whom to invite to chat
8300 void
8301 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
8302 struct sip_session *session,
8303 const gchar *who)
8305 /* a conference */
8306 if (session->focus_uri)
8308 sipe_invite_conf(sipe_private, session, who);
8310 else /* a multi-party chat */
8312 gchar *self = sip_uri_self(sipe_private);
8313 if (session->roster_manager) {
8314 if (sipe_strcase_equal(session->roster_manager, self)) {
8315 sipe_invite(sipe_private, session, who, NULL, NULL, NULL, FALSE);
8316 } else {
8317 sipe_refer(sipe_private, session, who);
8319 } else {
8320 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
8322 session->pending_invite_queue = slist_insert_unique_sorted(
8323 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
8325 sipe_election_start(sipe_private, session);
8327 g_free(self);
8331 void
8332 sipe_process_pending_invite_queue(struct sipe_core_private *sipe_private,
8333 struct sip_session *session)
8335 gchar *invitee;
8336 GSList *entry = session->pending_invite_queue;
8338 while (entry) {
8339 invitee = entry->data;
8340 sipe_invite_to_chat(sipe_private, session, invitee);
8341 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
8342 g_free(invitee);
8346 static void
8347 sipe_election_result(struct sipe_core_private *sipe_private,
8348 void *sess)
8350 struct sip_session *session = (struct sip_session *)sess;
8351 gchar *rival;
8352 gboolean has_won = TRUE;
8354 if (session->roster_manager) {
8355 SIPE_DEBUG_INFO(
8356 "sipe_election_result: RM has already been elected in the meantime. It is %s",
8357 session->roster_manager);
8358 return;
8361 session->is_voting_in_progress = FALSE;
8363 SIPE_DIALOG_FOREACH {
8364 if (dialog->election_vote < 0) {
8365 has_won = FALSE;
8366 rival = dialog->with;
8367 break;
8369 } SIPE_DIALOG_FOREACH_END;
8371 if (has_won) {
8372 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
8374 session->roster_manager = sip_uri_self(sipe_private);
8376 SIPE_DIALOG_FOREACH {
8377 /* send SetRM to each chat participant*/
8378 sipe_send_election_set_rm(sipe_private, dialog);
8379 } SIPE_DIALOG_FOREACH_END;
8380 } else {
8381 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
8383 session->bid = 0;
8385 sipe_process_pending_invite_queue(sipe_private, session);
8389 * For 2007+ conference only.
8391 static void
8392 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
8394 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
8395 struct sip_session *session;
8397 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
8398 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
8400 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
8402 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
8406 * For 2007+ conference only.
8408 static void
8409 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
8411 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
8412 struct sip_session *session;
8414 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
8415 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
8417 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
8419 sipe_conf_delete_user(sipe_private, session, buddy->name);
8422 static void
8423 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
8425 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
8426 struct sip_session *session;
8428 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
8429 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
8431 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
8433 sipe_invite_to_chat(sipe_private, session, buddy->name);
8436 static void
8437 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
8439 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
8441 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
8442 if (phone) {
8443 char *tel_uri = sip_to_tel_uri(phone);
8445 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
8446 sip_csta_make_call(sipe_private, tel_uri);
8448 g_free(tel_uri);
8452 static void
8453 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
8455 /** Translators: replace with URL to localized page
8456 * If it doesn't exist copy the original URL */
8457 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
8460 static void
8461 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
8463 const gchar *email;
8464 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
8466 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8467 if (email)
8469 char *command_line = g_strdup_printf(
8470 #ifdef _WIN32
8471 "cmd /c start"
8472 #else
8473 "xdg-email"
8474 #endif
8475 " mailto:%s", email);
8476 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
8478 g_spawn_command_line_async(command_line, NULL);
8479 g_free(command_line);
8481 else
8483 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
8487 static void
8488 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
8489 struct sipe_container *container)
8491 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
8492 struct sipe_container_member *member;
8494 if (!container || !container->members) return;
8496 member = ((struct sipe_container_member *)container->members->data);
8498 if (!member->type) return;
8500 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
8501 container->id, member->type, member->value ? member->value : "");
8503 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
8506 static GList *
8507 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
8508 const char* uri);
8511 * A menu which appear when right-clicking on buddy in contact list.
8513 GList *
8514 sipe_buddy_menu(PurpleBuddy *buddy)
8516 PurpleBlistNode *g_node;
8517 PurpleGroup *group, *gr_parent;
8518 PurpleMenuAction *act;
8519 GList *menu = NULL;
8520 GList *menu_groups = NULL;
8521 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
8522 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
8523 const char *email;
8524 const char *phone;
8525 const char *phone_disp_str;
8526 gchar *self = sip_uri_self(sipe_private);
8528 SIPE_SESSION_FOREACH {
8529 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->conv)
8531 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
8533 PurpleConvChatBuddyFlags flags;
8534 PurpleConvChatBuddyFlags flags_us;
8536 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
8537 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
8538 if (session->focus_uri
8539 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
8540 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8542 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
8543 act = purple_menu_action_new(label,
8544 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
8545 session->chat_title, NULL);
8546 g_free(label);
8547 menu = g_list_prepend(menu, act);
8550 if (session->focus_uri
8551 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8553 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
8554 act = purple_menu_action_new(label,
8555 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
8556 session->chat_title, NULL);
8557 g_free(label);
8558 menu = g_list_prepend(menu, act);
8561 else
8563 if (!session->focus_uri
8564 || (session->focus_uri && !session->locked))
8566 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
8567 act = purple_menu_action_new(label,
8568 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
8569 session->chat_title, NULL);
8570 g_free(label);
8571 menu = g_list_prepend(menu, act);
8575 } SIPE_SESSION_FOREACH_END;
8577 act = purple_menu_action_new(_("New chat"),
8578 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
8579 NULL, NULL);
8580 menu = g_list_prepend(menu, act);
8582 if (sip->csta && !sip->csta->line_status) {
8583 gchar *tmp = NULL;
8584 /* work phone */
8585 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
8586 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
8587 if (phone) {
8588 gchar *label = g_strdup_printf(_("Work %s"),
8589 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8590 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8591 g_free(tmp);
8592 tmp = NULL;
8593 g_free(label);
8594 menu = g_list_prepend(menu, act);
8597 /* mobile phone */
8598 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
8599 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
8600 if (phone) {
8601 gchar *label = g_strdup_printf(_("Mobile %s"),
8602 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8603 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8604 g_free(tmp);
8605 tmp = NULL;
8606 g_free(label);
8607 menu = g_list_prepend(menu, act);
8610 /* home phone */
8611 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
8612 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
8613 if (phone) {
8614 gchar *label = g_strdup_printf(_("Home %s"),
8615 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8616 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8617 g_free(tmp);
8618 tmp = NULL;
8619 g_free(label);
8620 menu = g_list_prepend(menu, act);
8623 /* other phone */
8624 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
8625 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
8626 if (phone) {
8627 gchar *label = g_strdup_printf(_("Other %s"),
8628 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8629 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8630 g_free(tmp);
8631 tmp = NULL;
8632 g_free(label);
8633 menu = g_list_prepend(menu, act);
8636 /* custom1 phone */
8637 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
8638 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
8639 if (phone) {
8640 gchar *label = g_strdup_printf(_("Custom1 %s"),
8641 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
8642 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
8643 g_free(tmp);
8644 tmp = NULL;
8645 g_free(label);
8646 menu = g_list_prepend(menu, act);
8650 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
8651 if (email) {
8652 act = purple_menu_action_new(_("Send email..."),
8653 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
8654 NULL, NULL);
8655 menu = g_list_prepend(menu, act);
8658 /* Access Level */
8659 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
8660 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
8662 act = purple_menu_action_new(_("Access level"),
8663 NULL,
8664 NULL, menu_access_levels);
8665 menu = g_list_prepend(menu, act);
8668 /* Copy to */
8669 gr_parent = purple_buddy_get_group(buddy);
8670 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
8671 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
8672 continue;
8674 group = (PurpleGroup *)g_node;
8675 if (group == gr_parent)
8676 continue;
8678 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
8679 continue;
8681 act = purple_menu_action_new(purple_group_get_name(group),
8682 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
8683 group->name, NULL);
8684 menu_groups = g_list_prepend(menu_groups, act);
8686 menu_groups = g_list_reverse(menu_groups);
8688 act = purple_menu_action_new(_("Copy to"),
8689 NULL,
8690 NULL, menu_groups);
8691 menu = g_list_prepend(menu, act);
8693 menu = g_list_reverse(menu);
8695 g_free(self);
8696 return menu;
8699 static void
8700 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
8702 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
8703 const char *domain = purple_request_fields_get_string(fields, "access_domain");
8704 int index = purple_request_fields_get_choice(fields, "container_id");
8705 /* move Blocked first */
8706 int i = (index == 4) ? 0 : index + 1;
8707 int container_id = containers[i];
8709 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
8711 sipe_change_access_level(sipe_private, container_id, "domain", domain);
8714 static void
8715 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
8717 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
8718 PurpleAccount *account = sip->account;
8719 PurpleConnection *gc = sip->gc;
8720 PurpleRequestFields *fields;
8721 PurpleRequestFieldGroup *g;
8722 PurpleRequestField *f;
8724 fields = purple_request_fields_new();
8726 g = purple_request_field_group_new(NULL);
8727 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
8728 purple_request_field_set_required(f, TRUE);
8729 purple_request_field_group_add_field(g, f);
8731 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
8732 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
8733 purple_request_field_choice_add(f, _("Team"));
8734 purple_request_field_choice_add(f, _("Company"));
8735 purple_request_field_choice_add(f, _("Public"));
8736 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
8737 purple_request_field_choice_set_default_value(f, 3); /* index */
8738 purple_request_field_set_required(f, TRUE);
8739 purple_request_field_group_add_field(g, f);
8741 purple_request_fields_add_group(fields, g);
8743 purple_request_fields(gc, _("Add new domain"),
8744 _("Add new domain"), NULL, fields,
8745 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
8746 _("Cancel"), NULL,
8747 account, NULL, NULL, gc);
8750 static void
8751 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
8753 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
8756 static GList *
8757 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
8758 const char* member_type,
8759 const char* member_value,
8760 const gboolean extra_menu)
8762 GList *menu_access_levels = NULL;
8763 unsigned int i;
8764 char *menu_name;
8765 PurpleMenuAction *act;
8766 struct sipe_container *container;
8767 struct sipe_container_member *member;
8768 gboolean is_group_access = FALSE;
8769 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
8771 for (i = 1; i <= CONTAINERS_LEN; i++) {
8772 /* to put Blocked level last in menu list.
8773 * Blocked should remaim in the first place in the containers[] array.
8775 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
8776 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
8778 container = g_new0(struct sipe_container, 1);
8779 member = g_new0(struct sipe_container_member, 1);
8780 container->id = containers[j];
8781 container->members = g_slist_append(container->members, member);
8782 member->type = g_strdup(member_type);
8783 member->value = g_strdup(member_value);
8785 /* current container/access level */
8786 if (((int)containers[j]) == container_id) {
8787 menu_name = is_group_access ?
8788 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
8789 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
8790 } else {
8791 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
8794 act = purple_menu_action_new(menu_name,
8795 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
8796 container, NULL);
8797 g_free(menu_name);
8798 menu_access_levels = g_list_prepend(menu_access_levels, act);
8801 if (extra_menu && (container_id >= 0)) {
8802 /* separator */
8803 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
8804 menu_access_levels = g_list_prepend(menu_access_levels, act);
8806 if (!is_group_access) {
8807 container = g_new0(struct sipe_container, 1);
8808 member = g_new0(struct sipe_container_member, 1);
8809 container->id = -1;
8810 container->members = g_slist_append(container->members, member);
8811 member->type = g_strdup(member_type);
8812 member->value = g_strdup(member_value);
8814 /* Translators: remove (clear) previously assigned access level */
8815 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
8816 act = purple_menu_action_new(menu_name,
8817 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
8818 container, NULL);
8819 g_free(menu_name);
8820 menu_access_levels = g_list_prepend(menu_access_levels, act);
8824 menu_access_levels = g_list_reverse(menu_access_levels);
8825 return menu_access_levels;
8828 static GList *
8829 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
8831 GList *menu_access_groups = NULL;
8832 PurpleMenuAction *act;
8833 GSList *access_domains = NULL;
8834 GSList *entry;
8835 char *menu_name;
8836 char *domain;
8838 act = purple_menu_action_new(_("People in my company"),
8839 NULL,
8840 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
8841 menu_access_groups = g_list_prepend(menu_access_groups, act);
8843 /* this is original name, don't edit */
8844 act = purple_menu_action_new(_("People in domains connected with my company"),
8845 NULL,
8846 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
8847 menu_access_groups = g_list_prepend(menu_access_groups, act);
8849 act = purple_menu_action_new(_("People in public domains"),
8850 NULL,
8851 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
8852 menu_access_groups = g_list_prepend(menu_access_groups, act);
8854 access_domains = sipe_get_access_domains(sipe_private);
8855 entry = access_domains;
8856 while (entry) {
8857 domain = entry->data;
8859 menu_name = g_strdup_printf(_("People at %s"), domain);
8860 act = purple_menu_action_new(menu_name,
8861 NULL,
8862 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
8863 menu_access_groups = g_list_prepend(menu_access_groups, act);
8864 g_free(menu_name);
8866 entry = entry->next;
8869 /* separator */
8870 /* People in domains connected with my company */
8871 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
8872 menu_access_groups = g_list_prepend(menu_access_groups, act);
8874 act = purple_menu_action_new(_("Add new domain..."),
8875 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
8876 NULL, NULL);
8877 menu_access_groups = g_list_prepend(menu_access_groups, act);
8879 menu_access_groups = g_list_reverse(menu_access_groups);
8881 return menu_access_groups;
8884 static GList *
8885 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
8886 const char* uri)
8888 GList *menu_access_levels = NULL;
8889 GList *menu_access_groups = NULL;
8890 char *menu_name;
8891 PurpleMenuAction *act;
8893 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
8895 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
8897 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
8898 act = purple_menu_action_new(menu_name,
8899 NULL,
8900 NULL, menu_access_groups);
8901 g_free(menu_name);
8902 menu_access_levels = g_list_append(menu_access_levels, act);
8904 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
8905 act = purple_menu_action_new(menu_name,
8906 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
8907 NULL, NULL);
8908 g_free(menu_name);
8909 menu_access_levels = g_list_append(menu_access_levels, act);
8911 return menu_access_levels;
8914 static void
8915 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
8917 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
8918 struct sip_session *session;
8920 session = sipe_session_find_chat_by_title(sipe_private,
8921 (gchar *)g_hash_table_lookup(chat->components, "channel"));
8922 sipe_conf_modify_conference_lock(sipe_private, session, locked);
8925 static void
8926 sipe_chat_menu_unlock_cb(PurpleChat *chat)
8928 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
8929 sipe_conf_modify_lock(chat, FALSE);
8932 static void
8933 sipe_chat_menu_lock_cb(PurpleChat *chat)
8935 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
8936 sipe_conf_modify_lock(chat, TRUE);
8939 GList *
8940 sipe_chat_menu(PurpleChat *chat)
8942 PurpleMenuAction *act;
8943 PurpleConvChatBuddyFlags flags_us;
8944 GList *menu = NULL;
8945 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
8946 struct sip_session *session;
8947 gchar *self;
8949 session = sipe_session_find_chat_by_title(sipe_private,
8950 (gchar *)g_hash_table_lookup(chat->components, "channel"));
8951 if (!session) return NULL;
8953 self = sip_uri_self(sipe_private);
8954 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
8956 if (session->focus_uri
8957 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
8959 if (session->locked) {
8960 act = purple_menu_action_new(_("Unlock"),
8961 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
8962 NULL, NULL);
8963 menu = g_list_prepend(menu, act);
8964 } else {
8965 act = purple_menu_action_new(_("Lock"),
8966 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
8967 NULL, NULL);
8968 menu = g_list_prepend(menu, act);
8972 menu = g_list_reverse(menu);
8974 g_free(self);
8975 return menu;
8978 static gboolean
8979 process_get_info_response(struct sipe_core_private *sipe_private,
8980 struct sipmsg *msg, struct transaction *trans)
8982 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
8983 char *uri = trans->payload->data;
8985 PurpleNotifyUserInfo *info;
8986 PurpleBuddy *pbuddy = NULL;
8987 struct sipe_buddy *sbuddy;
8988 const char *alias = NULL;
8989 char *device_name = NULL;
8990 char *server_alias = NULL;
8991 char *phone_number = NULL;
8992 char *email = NULL;
8993 const char *site;
8994 char *first_name = NULL;
8995 char *last_name = NULL;
8997 if (!sip) return FALSE;
8999 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
9001 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
9002 alias = purple_buddy_get_local_alias(pbuddy);
9004 //will query buddy UA's capabilities and send answer to log
9005 sipe_options_request(sipe_private, uri);
9007 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
9008 if (sbuddy) {
9009 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
9012 info = purple_notify_user_info_new();
9014 if (msg->response != 200) {
9015 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
9016 } else {
9017 sipe_xml *searchResults;
9018 const sipe_xml *mrow;
9020 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
9021 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
9022 if (!searchResults) {
9023 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
9024 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
9025 const char *value;
9026 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
9027 email = g_strdup(sipe_xml_attribute(mrow, "email"));
9028 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
9030 /* For 2007 system we will take this from ContactCard -
9031 * it has cleaner tel: URIs at least
9033 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
9034 char *tel_uri = sip_to_tel_uri(phone_number);
9035 /* trims its parameters, so call first */
9036 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, server_alias);
9037 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
9038 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
9039 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, phone_number);
9040 g_free(tel_uri);
9043 if (server_alias && strlen(server_alias) > 0) {
9044 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9046 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
9047 purple_notify_user_info_add_pair(info, _("Job title"), value);
9049 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
9050 purple_notify_user_info_add_pair(info, _("Office"), value);
9052 if (phone_number && strlen(phone_number) > 0) {
9053 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
9055 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
9056 purple_notify_user_info_add_pair(info, _("Company"), value);
9058 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
9059 purple_notify_user_info_add_pair(info, _("City"), value);
9061 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
9062 purple_notify_user_info_add_pair(info, _("State"), value);
9064 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
9065 purple_notify_user_info_add_pair(info, _("Country"), value);
9067 if (email && strlen(email) > 0) {
9068 purple_notify_user_info_add_pair(info, _("Email address"), email);
9072 sipe_xml_free(searchResults);
9075 purple_notify_user_info_add_section_break(info);
9077 if (is_empty(server_alias)) {
9078 g_free(server_alias);
9079 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
9080 if (server_alias) {
9081 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
9085 /* present alias if it differs from server alias */
9086 if (alias && !sipe_strequal(alias, server_alias))
9088 purple_notify_user_info_add_pair(info, _("Alias"), alias);
9091 if (is_empty(email)) {
9092 g_free(email);
9093 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
9094 if (email) {
9095 purple_notify_user_info_add_pair(info, _("Email address"), email);
9099 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
9100 if (site) {
9101 purple_notify_user_info_add_pair(info, _("Site"), site);
9104 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
9105 if (first_name && last_name) {
9106 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
9108 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
9109 g_free(link);
9111 g_free(first_name);
9112 g_free(last_name);
9114 if (device_name) {
9115 purple_notify_user_info_add_pair(info, _("Device"), device_name);
9118 /* show a buddy's user info in a nice dialog box */
9119 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
9120 uri, /* buddy's URI */
9121 info, /* body */
9122 NULL, /* callback called when dialog closed */
9123 NULL); /* userdata for callback */
9125 g_free(phone_number);
9126 g_free(server_alias);
9127 g_free(email);
9128 g_free(device_name);
9130 return TRUE;
9134 * AD search first, LDAP based
9136 void sipe_get_info(PurpleConnection *gc, const char *username)
9138 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
9139 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
9140 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
9141 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
9142 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
9144 payload->destroy = g_free;
9145 payload->data = g_strdup(username);
9147 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
9148 send_soap_request_with_cb(sipe_private, domain_uri, body,
9149 process_get_info_response, payload);
9150 g_free(domain_uri);
9151 g_free(body);
9152 g_free(row);
9156 Local Variables:
9157 mode: c
9158 c-file-style: "bsd"
9159 indent-tabs-mode: t
9160 tab-width: 8
9161 End: