Updated to release 1.7.1
[siplcs.git] / src / sipe.c
blobe5d1e56a846255f91b3aa6cfbc2fe27059760a85
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
8 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
12 * ***
13 * Thanks to Google's Summer of Code Program and the helpful mentors
14 * ***
16 * Session-based SIP MESSAGE documentation:
17 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34 #ifndef _WIN32
35 #include <sys/socket.h>
36 #include <sys/ioctl.h>
37 #include <sys/types.h>
38 #include <netinet/in.h>
39 #include <net/if.h>
40 #else
41 #ifdef _DLL
42 #define _WS2TCPIP_H_
43 #define _WINSOCK2API_
44 #define _LIBC_INTERNAL_
45 #endif /* _DLL */
46 #include "internal.h"
47 #endif /* _WIN32 */
49 #include <time.h>
50 #include <stdio.h>
51 #include <errno.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include <glib.h>
57 #include "accountopt.h"
58 #include "blist.h"
59 #include "conversation.h"
60 #include "dnsquery.h"
61 #include "debug.h"
62 #include "notify.h"
63 #include "privacy.h"
64 #include "prpl.h"
65 #include "plugin.h"
66 #include "util.h"
67 #include "version.h"
68 #include "network.h"
69 #include "xmlnode.h"
70 #include "mime.h"
72 #include "sipe.h"
73 #include "sipe-chat.h"
74 #include "sipe-conf.h"
75 #include "sip-csta.h"
76 #include "sipe-dialog.h"
77 #include "sipe-nls.h"
78 #include "sipe-session.h"
79 #include "sipe-utils.h"
80 #include "sipmsg.h"
81 #include "sipe-sign.h"
82 #include "dnssrv.h"
83 #include "request.h"
85 /* Backward compatibility when compiling against 2.4.x API */
86 #if !PURPLE_VERSION_CHECK(2,5,0)
87 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
88 #endif
90 /* Keep in sync with sipe_transport_type! */
91 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
92 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
94 /* Status identifiers (see also: sipe_status_types()) */
95 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
96 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
97 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
98 /* PURPLE_STATUS_UNAVAILABLE: */
99 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
100 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
101 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
102 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
103 /* PURPLE_STATUS_AWAY: */
104 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
105 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
106 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
107 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
108 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
109 /* ??? PURPLE_STATUS_MOBILE */
110 /* ??? PURPLE_STATUS_TUNE */
112 /* Status attributes (see also sipe_status_types() */
113 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
115 /* Action name templates */
116 #define ACTION_NAME_PRESENCE "<presence><%s>"
118 /* Our publication type keys. OCS 2007+
119 * Format: SIPE_PUB_{Category}[_{SubSategory}]
121 #define SIPE_PUB_DEVICE "000"
122 #define SIPE_PUB_STATE_MACHINE "100"
123 #define SIPE_PUB_STATE_USER "200"
125 /** Allows to send typed messages from chat window again after account reinstantiation. */
126 static void
127 sipe_rejoin_chat(PurpleConversation *conv)
129 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
130 PURPLE_CONV_CHAT(conv)->left)
132 PURPLE_CONV_CHAT(conv)->left = FALSE;
133 purple_conversation_update(conv, PURPLE_CONV_UPDATE_CHATLEFT);
137 static char *genbranch()
139 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
140 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
141 rand() & 0xFFFF, rand() & 0xFFFF);
145 static char *default_ua = NULL;
146 static const char*
147 sipe_get_useragent(struct sipe_account_data *sip)
149 const char *useragent = purple_account_get_string(sip->account, "useragent", "");
150 if (is_empty(useragent)) {
151 if (!default_ua) {
152 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
153 /* ref: lzodefs.h */
154 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
155 #define SIPE_TARGET_PLATFORM "linux"
156 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
157 #define SIPE_TARGET_PLATFORM "bsd"
158 # elif defined(__APPLE__) || defined(__MACOS__)
159 #define SIPE_TARGET_PLATFORM "macosx"
160 #elif defined(__solaris__) || defined(__sun)
161 #define SIPE_TARGET_PLATFORM "sun"
162 #elif defined(_WIN32)
163 #define SIPE_TARGET_PLATFORM "win"
164 #else
165 #define SIPE_TARGET_PLATFORM "generic"
166 #endif
168 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
169 #define SIPE_TARGET_ARCH "x86_64"
170 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
171 #define SIPE_TARGET_ARCH "i386"
172 #elif defined(__ppc64__)
173 #define SIPE_TARGET_ARCH "ppc64"
174 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
175 #define SIPE_TARGET_ARCH "ppc"
176 #else
177 #define SIPE_TARGET_ARCH "other"
178 #endif
180 default_ua = g_strdup_printf("Purple/%d.%d.%d Sipe/%s (%s-%s; %s)",
181 PURPLE_MAJOR_VERSION,
182 PURPLE_MINOR_VERSION,
183 PURPLE_MICRO_VERSION,
184 SIPE_VERSION,
185 SIPE_TARGET_PLATFORM,
186 SIPE_TARGET_ARCH,
187 sip->server_version ? sip->server_version : "");
189 useragent = default_ua;
191 return useragent;
194 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
195 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
197 return "sipe";
200 static void sipe_plugin_destroy(PurplePlugin *plugin);
202 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans);
204 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
205 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
206 gpointer data);
208 static void sipe_close(PurpleConnection *gc);
210 static void send_presence_status(struct sipe_account_data *sip);
212 static void sendout_pkt(PurpleConnection *gc, const char *buf);
214 static void sipe_keep_alive(PurpleConnection *gc)
216 struct sipe_account_data *sip = gc->proto_data;
217 if (sip->transport == SIPE_TRANSPORT_UDP) {
218 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
219 gchar buf[2] = {0, 0};
220 purple_debug_info("sipe", "sending keep alive\n");
221 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
222 } else {
223 time_t now = time(NULL);
224 if ((sip->keepalive_timeout > 0) &&
225 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
226 #if PURPLE_VERSION_CHECK(2,4,0)
227 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
228 #endif
230 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
231 sendout_pkt(gc, "\r\n\r\n");
232 sip->last_keepalive = now;
237 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
239 struct sip_connection *ret = NULL;
240 GSList *entry = sip->openconns;
241 while (entry) {
242 ret = entry->data;
243 if (ret->fd == fd) return ret;
244 entry = entry->next;
246 return NULL;
249 static void sipe_auth_free(struct sip_auth *auth)
251 g_free(auth->opaque);
252 auth->opaque = NULL;
253 g_free(auth->realm);
254 auth->realm = NULL;
255 g_free(auth->target);
256 auth->target = NULL;
257 auth->type = AUTH_TYPE_UNSET;
258 auth->retries = 0;
259 auth->expires = 0;
260 g_free(auth->gssapi_data);
261 auth->gssapi_data = NULL;
262 sip_sec_destroy_context(auth->gssapi_context);
263 auth->gssapi_context = NULL;
266 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
268 struct sip_connection *ret = g_new0(struct sip_connection, 1);
269 ret->fd = fd;
270 sip->openconns = g_slist_append(sip->openconns, ret);
271 return ret;
274 static void connection_remove(struct sipe_account_data *sip, int fd)
276 struct sip_connection *conn = connection_find(sip, fd);
277 if (conn) {
278 sip->openconns = g_slist_remove(sip->openconns, conn);
279 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
280 g_free(conn->inbuf);
281 g_free(conn);
285 static void connection_free_all(struct sipe_account_data *sip)
287 struct sip_connection *ret = NULL;
288 GSList *entry = sip->openconns;
289 while (entry) {
290 ret = entry->data;
291 connection_remove(sip, ret->fd);
292 entry = sip->openconns;
296 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
298 gchar noncecount[9];
299 const char *authuser = sip->authuser;
300 gchar *response;
301 gchar *ret;
303 if (!authuser || strlen(authuser) < 1) {
304 authuser = sip->username;
307 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
308 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
310 // If we have a signature for the message, include that
311 if (msg->signature) {
312 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);
315 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
316 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
317 gchar *gssapi_data;
318 gchar *opaque;
320 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
321 &(auth->expires),
322 auth->type,
323 purple_account_get_bool(sip->account, "sso", TRUE),
324 sip->authdomain ? sip->authdomain : "",
325 authuser,
326 sip->password,
327 auth->target,
328 auth->gssapi_data);
329 if (!gssapi_data || !auth->gssapi_context) {
330 sip->gc->wants_to_die = TRUE;
331 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
332 return NULL;
335 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
336 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
337 g_free(opaque);
338 g_free(gssapi_data);
339 return ret;
342 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
344 } else { /* Digest */
346 /* Calculate new session key */
347 if (!auth->opaque) {
348 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
349 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
350 authuser, auth->realm, sip->password,
351 auth->gssapi_data, NULL);
354 sprintf(noncecount, "%08d", auth->nc++);
355 response = purple_cipher_http_digest_calculate_response("md5",
356 msg->method, msg->target, NULL, NULL,
357 auth->gssapi_data, noncecount, NULL,
358 auth->opaque);
359 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
361 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->gssapi_data, msg->target, noncecount, response);
362 g_free(response);
363 return ret;
367 static char *parse_attribute(const char *attrname, const char *source)
369 const char *tmp, *tmp2;
370 char *retval = NULL;
371 int len = strlen(attrname);
373 if (!strncmp(source, attrname, len)) {
374 tmp = source + len;
375 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
376 if (tmp2)
377 retval = g_strndup(tmp, tmp2 - tmp);
378 else
379 retval = g_strdup(tmp);
382 return retval;
385 static void fill_auth(gchar *hdr, struct sip_auth *auth)
387 int i;
388 gchar **parts;
390 if (!hdr) {
391 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
392 return;
395 if (!g_strncasecmp(hdr, "NTLM", 4)) {
396 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
397 auth->type = AUTH_TYPE_NTLM;
398 hdr += 5;
399 auth->nc = 1;
400 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
401 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
402 auth->type = AUTH_TYPE_KERBEROS;
403 hdr += 9;
404 auth->nc = 3;
405 } else {
406 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
407 auth->type = AUTH_TYPE_DIGEST;
408 hdr += 7;
411 parts = g_strsplit(hdr, "\", ", 0);
412 for (i = 0; parts[i]; i++) {
413 char *tmp;
415 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
417 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
418 g_free(auth->gssapi_data);
419 auth->gssapi_data = tmp;
421 if (auth->type == AUTH_TYPE_NTLM) {
422 /* NTLM module extracts nonce from gssapi-data */
423 auth->nc = 3;
426 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
427 /* Only used with AUTH_TYPE_DIGEST */
428 g_free(auth->gssapi_data);
429 auth->gssapi_data = tmp;
430 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
431 g_free(auth->opaque);
432 auth->opaque = tmp;
433 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
434 g_free(auth->realm);
435 auth->realm = tmp;
437 if (auth->type == AUTH_TYPE_DIGEST) {
438 /* Throw away old session key */
439 g_free(auth->opaque);
440 auth->opaque = NULL;
441 auth->nc = 1;
444 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
445 g_free(auth->target);
446 auth->target = tmp;
449 g_strfreev(parts);
451 return;
454 static void sipe_canwrite_cb(gpointer data,
455 SIPE_UNUSED_PARAMETER gint source,
456 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
458 PurpleConnection *gc = data;
459 struct sipe_account_data *sip = gc->proto_data;
460 gsize max_write;
461 gssize written;
463 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
465 if (max_write == 0) {
466 if (sip->tx_handler != 0){
467 purple_input_remove(sip->tx_handler);
468 sip->tx_handler = 0;
470 return;
473 written = write(sip->fd, sip->txbuf->outptr, max_write);
475 if (written < 0 && errno == EAGAIN)
476 written = 0;
477 else if (written <= 0) {
478 /*TODO: do we really want to disconnect on a failure to write?*/
479 purple_connection_error(gc, _("Could not write"));
480 return;
483 purple_circ_buffer_mark_read(sip->txbuf, written);
486 static void sipe_canwrite_cb_ssl(gpointer data,
487 SIPE_UNUSED_PARAMETER gint src,
488 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
490 PurpleConnection *gc = data;
491 struct sipe_account_data *sip = gc->proto_data;
492 gsize max_write;
493 gssize written;
495 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
497 if (max_write == 0) {
498 if (sip->tx_handler != 0) {
499 purple_input_remove(sip->tx_handler);
500 sip->tx_handler = 0;
501 return;
505 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
507 if (written < 0 && errno == EAGAIN)
508 written = 0;
509 else if (written <= 0) {
510 /*TODO: do we really want to disconnect on a failure to write?*/
511 purple_connection_error(gc, _("Could not write"));
512 return;
515 purple_circ_buffer_mark_read(sip->txbuf, written);
518 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
520 static void send_later_cb(gpointer data, gint source,
521 SIPE_UNUSED_PARAMETER const gchar *error)
523 PurpleConnection *gc = data;
524 struct sipe_account_data *sip;
525 struct sip_connection *conn;
527 if (!PURPLE_CONNECTION_IS_VALID(gc))
529 if (source >= 0)
530 close(source);
531 return;
534 if (source < 0) {
535 purple_connection_error(gc, _("Could not connect"));
536 return;
539 sip = gc->proto_data;
540 sip->fd = source;
541 sip->connecting = FALSE;
542 sip->last_keepalive = time(NULL);
544 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
546 /* If there is more to write now, we need to register a handler */
547 if (sip->txbuf->bufused > 0)
548 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
550 conn = connection_create(sip, source);
551 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
554 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
556 struct sipe_account_data *sip;
557 struct sip_connection *conn;
559 if (!PURPLE_CONNECTION_IS_VALID(gc))
561 if (gsc) purple_ssl_close(gsc);
562 return NULL;
565 sip = gc->proto_data;
566 sip->fd = gsc->fd;
567 sip->gsc = gsc;
568 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
569 sip->connecting = FALSE;
570 sip->last_keepalive = time(NULL);
572 conn = connection_create(sip, gsc->fd);
574 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
576 return sip;
579 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
580 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
582 PurpleConnection *gc = data;
583 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
584 if (sip == NULL) return;
586 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
588 /* If there is more to write now */
589 if (sip->txbuf->bufused > 0) {
590 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
595 static void sendlater(PurpleConnection *gc, const char *buf)
597 struct sipe_account_data *sip = gc->proto_data;
599 if (!sip->connecting) {
600 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
601 if (sip->transport == SIPE_TRANSPORT_TLS){
602 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
603 } else {
604 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
605 purple_connection_error(gc, _("Could not create socket"));
608 sip->connecting = TRUE;
611 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
612 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
614 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
617 static void sendout_pkt(PurpleConnection *gc, const char *buf)
619 struct sipe_account_data *sip = gc->proto_data;
620 time_t currtime = time(NULL);
621 int writelen = strlen(buf);
622 char *tmp;
624 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sending - %s######\n%s######\n", ctime(&currtime), tmp = fix_newlines(buf));
625 g_free(tmp);
626 if (sip->transport == SIPE_TRANSPORT_UDP) {
627 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
628 purple_debug_info("sipe", "could not send packet\n");
630 } else {
631 int ret;
632 if (sip->fd < 0) {
633 sendlater(gc, buf);
634 return;
637 if (sip->tx_handler) {
638 ret = -1;
639 errno = EAGAIN;
640 } else{
641 if (sip->gsc){
642 ret = purple_ssl_write(sip->gsc, buf, writelen);
643 }else{
644 ret = write(sip->fd, buf, writelen);
648 if (ret < 0 && errno == EAGAIN)
649 ret = 0;
650 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
651 sendlater(gc, buf);
652 return;
655 if (ret < writelen) {
656 if (!sip->tx_handler){
657 if (sip->gsc){
658 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
660 else{
661 sip->tx_handler = purple_input_add(sip->fd,
662 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
663 gc);
667 /* XXX: is it OK to do this? You might get part of a request sent
668 with part of another. */
669 if (sip->txbuf->bufused > 0)
670 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
672 purple_circ_buffer_append(sip->txbuf, buf + ret,
673 writelen - ret);
678 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
680 sendout_pkt(gc, buf);
681 return len;
684 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
686 GSList *tmp = msg->headers;
687 gchar *name;
688 gchar *value;
689 GString *outstr = g_string_new("");
690 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
691 while (tmp) {
692 name = ((struct siphdrelement*) (tmp->data))->name;
693 value = ((struct siphdrelement*) (tmp->data))->value;
694 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
695 tmp = g_slist_next(tmp);
697 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
698 sendout_pkt(sip->gc, outstr->str);
699 g_string_free(outstr, TRUE);
702 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
704 gchar * buf;
706 if (sip->registrar.type == AUTH_TYPE_UNSET) {
707 return;
710 if (sip->registrar.gssapi_context) {
711 struct sipmsg_breakdown msgbd;
712 gchar *signature_input_str;
713 msgbd.msg = msg;
714 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
715 msgbd.rand = g_strdup_printf("%08x", g_random_int());
716 sip->registrar.ntlm_num++;
717 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
718 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
719 if (signature_input_str != NULL) {
720 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
721 msg->signature = signature_hex;
722 msg->rand = g_strdup(msgbd.rand);
723 msg->num = g_strdup(msgbd.num);
724 g_free(signature_input_str);
726 sipmsg_breakdown_free(&msgbd);
729 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
730 buf = auth_header(sip, &sip->registrar, msg);
731 if (buf) {
732 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
734 g_free(buf);
735 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS") || !strcmp(method, "REFER")) {
736 sip->registrar.nc = 3;
737 #ifdef USE_KERBEROS
738 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
739 #endif
740 sip->registrar.type = AUTH_TYPE_NTLM;
741 #ifdef USE_KERBEROS
742 } else {
743 sip->registrar.type = AUTH_TYPE_KERBEROS;
745 #endif
748 buf = auth_header(sip, &sip->registrar, msg);
749 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
750 g_free(buf);
751 } else {
752 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
756 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
757 const char *text, const char *body)
759 gchar *name;
760 gchar *value;
761 GString *outstr = g_string_new("");
762 struct sipe_account_data *sip = gc->proto_data;
763 gchar *contact;
764 GSList *tmp;
765 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
767 contact = get_contact(sip);
768 sipmsg_add_header(msg, "Contact", contact);
769 g_free(contact);
771 if (body) {
772 gchar len[12];
773 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
774 sipmsg_add_header(msg, "Content-Length", len);
775 } else {
776 sipmsg_add_header(msg, "Content-Length", "0");
779 msg->response = code;
781 sipmsg_strip_headers(msg, keepers);
782 sipmsg_merge_new_headers(msg);
783 sign_outgoing_message(msg, sip, msg->method);
785 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
786 tmp = msg->headers;
787 while (tmp) {
788 name = ((struct siphdrelement*) (tmp->data))->name;
789 value = ((struct siphdrelement*) (tmp->data))->value;
791 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
792 tmp = g_slist_next(tmp);
794 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
795 sendout_pkt(gc, outstr->str);
796 g_string_free(outstr, TRUE);
799 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
801 sip->transactions = g_slist_remove(sip->transactions, trans);
802 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip->transactions));
803 if (trans->msg) sipmsg_free(trans->msg);
804 if (trans->payload) {
805 (*trans->payload->destroy)(trans->payload->data);
806 g_free(trans->payload);
808 g_free(trans->key);
809 g_free(trans);
812 static struct transaction *
813 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
815 gchar *call_id = NULL;
816 gchar *cseq = NULL;
817 struct transaction *trans = g_new0(struct transaction, 1);
819 trans->time = time(NULL);
820 trans->msg = (struct sipmsg *)msg;
821 call_id = sipmsg_find_header(trans->msg, "Call-ID");
822 cseq = sipmsg_find_header(trans->msg, "CSeq");
823 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
824 trans->callback = callback;
825 sip->transactions = g_slist_append(sip->transactions, trans);
826 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip->transactions));
827 return trans;
830 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
832 struct transaction *trans;
833 GSList *transactions = sip->transactions;
834 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
835 gchar *cseq = sipmsg_find_header(msg, "CSeq");
836 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
838 while (transactions) {
839 trans = transactions->data;
840 if (!g_strcasecmp(trans->key, key)) {
841 g_free(key);
842 return trans;
844 transactions = transactions->next;
847 g_free(key);
848 return NULL;
851 struct transaction *
852 send_sip_request(PurpleConnection *gc, const gchar *method,
853 const gchar *url, const gchar *to, const gchar *addheaders,
854 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
856 struct sipe_account_data *sip = gc->proto_data;
857 const char *addh = "";
858 char *buf;
859 struct sipmsg *msg;
860 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
861 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
862 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
863 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
864 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
865 gchar *route = g_strdup("");
866 gchar *epid = get_epid(sip);
867 int cseq = dialog ? ++dialog->cseq : 1 /* as Call-Id is new in this case */;
868 struct transaction *trans = NULL;
870 if (dialog && dialog->routes)
872 GSList *iter = dialog->routes;
874 while(iter)
876 char *tmp = route;
877 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
878 g_free(tmp);
879 iter = g_slist_next(iter);
883 if (!ourtag && !dialog) {
884 ourtag = gentag();
887 if (!strcmp(method, "REGISTER")) {
888 if (sip->regcallid) {
889 g_free(callid);
890 callid = g_strdup(sip->regcallid);
891 } else {
892 sip->regcallid = g_strdup(callid);
894 cseq = ++sip->cseq;
897 if (addheaders) addh = addheaders;
899 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
900 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
901 "From: <sip:%s>%s%s;epid=%s\r\n"
902 "To: <%s>%s%s%s%s\r\n"
903 "Max-Forwards: 70\r\n"
904 "CSeq: %d %s\r\n"
905 "User-Agent: %s\r\n"
906 "Call-ID: %s\r\n"
907 "%s%s"
908 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
909 method,
910 dialog && dialog->request ? dialog->request : url,
911 TRANSPORT_DESCRIPTOR,
912 purple_network_get_my_ip(-1),
913 sip->listenport,
914 branch ? ";branch=" : "",
915 branch ? branch : "",
916 sip->username,
917 ourtag ? ";tag=" : "",
918 ourtag ? ourtag : "",
919 epid,
921 theirtag ? ";tag=" : "",
922 theirtag ? theirtag : "",
923 theirepid ? ";epid=" : "",
924 theirepid ? theirepid : "",
925 cseq,
926 method,
927 sipe_get_useragent(sip),
928 callid,
929 route,
930 addh,
931 body ? (gsize) strlen(body) : 0,
932 body ? body : "");
935 //printf ("parsing msg buf:\n%s\n\n", buf);
936 msg = sipmsg_parse_msg(buf);
938 g_free(buf);
939 g_free(ourtag);
940 g_free(theirtag);
941 g_free(theirepid);
942 g_free(branch);
943 g_free(callid);
944 g_free(route);
945 g_free(epid);
947 sign_outgoing_message (msg, sip, method);
949 buf = sipmsg_to_string (msg);
951 /* add to ongoing transactions */
952 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
953 if (strcmp(method, "ACK")) {
954 trans = transactions_add_buf(sip, msg, tc);
955 } else {
956 sipmsg_free(msg);
958 sendout_pkt(gc, buf);
959 g_free(buf);
961 return trans;
965 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
967 static void
968 send_soap_request_with_cb(struct sipe_account_data *sip,
969 gchar *from0,
970 gchar *body,
971 TransCallback callback,
972 struct transaction_payload *payload)
974 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sip);
975 gchar *contact = get_contact(sip);
976 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
977 "Content-Type: application/SOAP+xml\r\n",contact);
979 struct transaction *trans = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
980 trans->payload = payload;
982 g_free(from);
983 g_free(contact);
984 g_free(hdr);
987 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
989 send_soap_request_with_cb(sip, NULL, body, NULL, NULL);
992 static char *get_contact_register(struct sipe_account_data *sip)
994 char *epid = get_epid(sip);
995 char *uuid = generateUUIDfromEPID(epid);
996 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
997 g_free(uuid);
998 g_free(epid);
999 return(buf);
1002 static void do_register_exp(struct sipe_account_data *sip, int expire)
1004 char *uri;
1005 char *expires;
1006 char *to;
1007 char *contact;
1008 char *hdr;
1010 if (!sip->sipdomain) return;
1012 uri = sip_uri_from_name(sip->sipdomain);
1013 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1014 to = sip_uri_self(sip);
1015 contact = get_contact_register(sip);
1016 hdr = g_strdup_printf("Contact: %s\r\n"
1017 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1018 "Event: registration\r\n"
1019 "Allow-Events: presence\r\n"
1020 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1021 "%s", contact, expires);
1022 g_free(contact);
1023 g_free(expires);
1025 sip->registerstatus = 1;
1027 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1028 process_register_response);
1030 g_free(hdr);
1031 g_free(uri);
1032 g_free(to);
1035 static void do_register_cb(struct sipe_account_data *sip,
1036 SIPE_UNUSED_PARAMETER void *unused)
1038 do_register_exp(sip, -1);
1039 sip->reregister_set = FALSE;
1042 static void do_register(struct sipe_account_data *sip)
1044 do_register_exp(sip, -1);
1047 static void
1048 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1050 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1051 send_soap_request(sip, body);
1052 g_free(body);
1055 static void
1056 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1058 if (allow) {
1059 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1060 } else {
1061 purple_debug_info("sipe", "Blocking contact %s\n", who);
1064 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1067 static
1068 void sipe_auth_user_cb(void * data)
1070 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1071 if (!job) return;
1073 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1074 g_free(job);
1077 static
1078 void sipe_deny_user_cb(void * data)
1080 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1081 if (!job) return;
1083 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1084 g_free(job);
1087 static void
1088 sipe_add_permit(PurpleConnection *gc, const char *name)
1090 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1091 sipe_contact_allow_deny(sip, name, TRUE);
1094 static void
1095 sipe_add_deny(PurpleConnection *gc, const char *name)
1097 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1098 sipe_contact_allow_deny(sip, name, FALSE);
1101 /*static void
1102 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1104 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1105 sipe_contact_set_acl(sip, name, "");
1108 static void
1109 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1111 xmlnode *watchers;
1112 xmlnode *watcher;
1113 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1114 if (msg->response != 0 && msg->response != 200) return;
1116 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1118 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1119 if (!watchers) return;
1121 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1122 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1123 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1124 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1126 // TODO pull out optional displayName to pass as alias
1127 if (remote_user) {
1128 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1129 job->who = remote_user;
1130 job->sip = sip;
1131 purple_account_request_authorization(
1132 sip->account,
1133 remote_user,
1134 _("you"), /* id */
1135 alias,
1136 NULL, /* message */
1137 on_list,
1138 sipe_auth_user_cb,
1139 sipe_deny_user_cb,
1140 (void *) job);
1145 xmlnode_free(watchers);
1146 return;
1149 static void
1150 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1152 PurpleGroup * purple_group = purple_find_group(group->name);
1153 if (!purple_group) {
1154 purple_group = purple_group_new(group->name);
1155 purple_blist_add_group(purple_group, NULL);
1158 if (purple_group) {
1159 group->purple_group = purple_group;
1160 sip->groups = g_slist_append(sip->groups, group);
1161 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1162 } else {
1163 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1167 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1169 struct sipe_group *group;
1170 GSList *entry;
1171 if (sip == NULL) {
1172 return NULL;
1175 entry = sip->groups;
1176 while (entry) {
1177 group = entry->data;
1178 if (group->id == id) {
1179 return group;
1181 entry = entry->next;
1183 return NULL;
1186 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1188 struct sipe_group *group;
1189 GSList *entry;
1190 if (sip == NULL) {
1191 return NULL;
1194 entry = sip->groups;
1195 while (entry) {
1196 group = entry->data;
1197 if (!strcmp(group->name, name)) {
1198 return group;
1200 entry = entry->next;
1202 return NULL;
1205 static void
1206 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1208 gchar *body;
1209 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1210 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1211 send_soap_request(sip, body);
1212 g_free(body);
1213 g_free(group->name);
1214 group->name = g_strdup(name);
1218 * Only appends if no such value already stored.
1219 * Like Set in Java.
1221 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1222 GSList * res = list;
1223 if (!g_slist_find_custom(list, data, func)) {
1224 res = g_slist_insert_sorted(list, data, func);
1226 return res;
1229 static int
1230 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1231 return group1->id - group2->id;
1235 * Returns string like "2 4 7 8" - group ids buddy belong to.
1237 static gchar *
1238 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1239 int i = 0;
1240 gchar *res;
1241 //creating array from GList, converting int to gchar*
1242 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1243 GSList *entry = buddy->groups;
1244 while (entry) {
1245 struct sipe_group * group = entry->data;
1246 ids_arr[i] = g_strdup_printf("%d", group->id);
1247 entry = entry->next;
1248 i++;
1250 ids_arr[i] = NULL;
1251 res = g_strjoinv(" ", ids_arr);
1252 g_strfreev(ids_arr);
1253 return res;
1257 * Sends buddy update to server
1259 static void
1260 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1262 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1263 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1265 if (buddy && purple_buddy) {
1266 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1267 gchar *body;
1268 gchar *groups = sipe_get_buddy_groups_string(buddy);
1269 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1271 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1272 alias, groups, "true", buddy->name, sip->contacts_delta++
1274 send_soap_request(sip, body);
1275 g_free(groups);
1276 g_free(body);
1280 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1282 if (msg->response == 200) {
1283 struct sipe_group *group;
1284 struct group_user_context *ctx = trans->payload->data;
1285 xmlnode *xml;
1286 xmlnode *node;
1287 char *group_id;
1288 struct sipe_buddy *buddy;
1290 xml = xmlnode_from_str(msg->body, msg->bodylen);
1291 if (!xml) {
1292 return FALSE;
1295 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1296 if (!node) {
1297 xmlnode_free(xml);
1298 return FALSE;
1301 group_id = xmlnode_get_data(node);
1302 if (!group_id) {
1303 xmlnode_free(xml);
1304 return FALSE;
1307 group = g_new0(struct sipe_group, 1);
1308 group->id = (int)g_ascii_strtod(group_id, NULL);
1309 g_free(group_id);
1310 group->name = g_strdup(ctx->group_name);
1312 sipe_group_add(sip, group);
1314 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1315 if (buddy) {
1316 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1319 sipe_group_set_user(sip, ctx->user_name);
1321 xmlnode_free(xml);
1322 return TRUE;
1324 return FALSE;
1327 static void sipe_group_context_destroy(gpointer data)
1329 struct group_user_context *ctx = data;
1330 g_free(ctx->group_name);
1331 g_free(ctx->user_name);
1332 g_free(ctx);
1335 static void sipe_group_create (struct sipe_account_data *sip, const gchar *name, const gchar * who)
1337 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1338 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
1339 gchar *body;
1340 ctx->group_name = g_strdup(name);
1341 ctx->user_name = g_strdup(who);
1342 payload->destroy = sipe_group_context_destroy;
1343 payload->data = ctx;
1345 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1346 send_soap_request_with_cb(sip, NULL, body, process_add_group_response, payload);
1347 g_free(body);
1351 * Data structure for scheduled actions
1354 struct scheduled_action {
1356 * Name of action.
1357 * Format is <Event>[<Data>...]
1358 * Example: <presence><sip:user@domain.com> or <registration>
1360 gchar *name;
1361 guint timeout_handler;
1362 gboolean repetitive;
1363 Action action;
1364 GDestroyNotify destroy;
1365 struct sipe_account_data *sip;
1366 void *payload;
1370 * A timer callback
1371 * Should return FALSE if repetitive action is not needed
1373 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1375 gboolean ret;
1376 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1377 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1378 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1379 (sched_action->action)(sched_action->sip, sched_action->payload);
1380 ret = sched_action->repetitive;
1381 if (sched_action->destroy) {
1382 (*sched_action->destroy)(sched_action->payload);
1384 g_free(sched_action->name);
1385 g_free(sched_action);
1386 return ret;
1390 * Kills action timer effectively cancelling
1391 * scheduled action
1393 * @param name of action
1395 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1397 GSList *entry;
1399 if (!sip->timeouts || !name) return;
1401 entry = sip->timeouts;
1402 while (entry) {
1403 struct scheduled_action *sched_action = entry->data;
1404 if(!strcmp(sched_action->name, name)) {
1405 GSList *to_delete = entry;
1406 entry = entry->next;
1407 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1408 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1409 purple_timeout_remove(sched_action->timeout_handler);
1410 if (sched_action->destroy) {
1411 (*sched_action->destroy)(sched_action->payload);
1413 g_free(sched_action->name);
1414 g_free(sched_action);
1415 } else {
1416 entry = entry->next;
1421 static void
1422 sipe_schedule_action0(const gchar *name,
1423 int timeout,
1424 gboolean isSeconds,
1425 Action action,
1426 GDestroyNotify destroy,
1427 struct sipe_account_data *sip,
1428 void *payload)
1430 struct scheduled_action *sched_action;
1432 /* Make sure each action only exists once */
1433 sipe_cancel_scheduled_action(sip, name);
1435 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1436 sched_action = g_new0(struct scheduled_action, 1);
1437 sched_action->repetitive = FALSE;
1438 sched_action->name = g_strdup(name);
1439 sched_action->action = action;
1440 sched_action->destroy = destroy;
1441 sched_action->sip = sip;
1442 sched_action->payload = payload;
1443 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1444 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1445 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1446 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1449 void
1450 sipe_schedule_action(const gchar *name,
1451 int timeout,
1452 Action action,
1453 GDestroyNotify destroy,
1454 struct sipe_account_data *sip,
1455 void *payload)
1457 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1461 * Same as sipe_schedule_action() but timeout is in milliseconds.
1463 static void
1464 sipe_schedule_action_msec(const gchar *name,
1465 int timeout,
1466 Action action,
1467 GDestroyNotify destroy,
1468 struct sipe_account_data *sip,
1469 void *payload)
1471 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1475 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1477 /** Should be g_free()'d
1479 static gchar *
1480 sipe_get_subscription_key(gchar *event,
1481 gchar *with)
1483 gchar *key = NULL;
1485 if (is_empty(event)) return NULL;
1487 if (event && !g_ascii_strcasecmp(event, "presence")) {
1488 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1489 key = g_strdup_printf(ACTION_NAME_PRESENCE, with);
1491 /* @TODO drop participated buddies' just_added flag */
1492 } else if (event) {
1493 /* Subscription is identified by <event> key */
1494 key = g_strdup_printf("<%s>", event);
1497 return key;
1500 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1501 SIPE_UNUSED_PARAMETER struct transaction *trans)
1503 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
1504 gchar *event = sipmsg_find_header(msg, "Event");
1505 gchar *key = sipe_get_subscription_key(event, with);
1507 /* 200 OK; 481 Call Leg Does Not Exist */
1508 if (key && (msg->response == 200 || msg->response == 481)) {
1509 if (g_hash_table_lookup(sip->subscriptions, key)) {
1510 g_hash_table_remove(sip->subscriptions, key);
1511 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
1515 /* create/store subscription dialog if not yet */
1516 if (msg->response == 200) {
1517 gchar *callid = sipmsg_find_header(msg, "Call-ID");
1518 gchar *cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
1520 if (key) {
1521 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
1522 g_hash_table_insert(sip->subscriptions, g_strdup(key), subscription);
1524 subscription->dialog.callid = g_strdup(callid);
1525 subscription->dialog.cseq = atoi(cseq);
1526 subscription->dialog.with = g_strdup(with);
1527 subscription->event = g_strdup(event);
1528 sipe_dialog_parse(&subscription->dialog, msg, TRUE);
1530 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key);
1533 g_free(cseq);
1536 g_free(key);
1537 g_free(with);
1539 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1541 process_incoming_notify(sip, msg, FALSE, FALSE);
1543 return TRUE;
1546 static void sipe_subscribe_resource_uri(const char *name,
1547 SIPE_UNUSED_PARAMETER gpointer value,
1548 gchar **resources_uri)
1550 gchar *tmp = *resources_uri;
1551 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1552 g_free(tmp);
1555 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1557 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1558 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1559 gchar *tmp = *resources_uri;
1561 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
1563 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
1564 g_free(tmp);
1568 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1569 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1570 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1571 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1572 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1575 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1577 gchar *key;
1578 gchar *contact = get_contact(sip);
1579 gchar *request;
1580 gchar *content;
1581 gchar *require = "";
1582 gchar *accept = "";
1583 gchar *autoextend = "";
1584 gchar *content_type;
1585 struct sip_dialog *dialog;
1587 if (sip->ocs2007) {
1588 require = ", categoryList";
1589 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1590 content_type = "application/msrtc-adrl-categorylist+xml";
1591 content = g_strdup_printf(
1592 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1593 "<action name=\"subscribe\" id=\"63792024\">\n"
1594 "<adhocList>\n%s</adhocList>\n"
1595 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1596 "<category name=\"calendarData\"/>\n"
1597 "<category name=\"contactCard\"/>\n"
1598 "<category name=\"note\"/>\n"
1599 "<category name=\"state\"/>\n"
1600 "</categoryList>\n"
1601 "</action>\n"
1602 "</batchSub>", sip->username, resources_uri);
1603 } else {
1604 autoextend = "Supported: com.microsoft.autoextend\r\n";
1605 content_type = "application/adrl+xml";
1606 content = g_strdup_printf(
1607 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1608 "<create xmlns=\"\">\n%s</create>\n"
1609 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1611 g_free(resources_uri);
1613 request = g_strdup_printf(
1614 "Require: adhoclist%s\r\n"
1615 "Supported: eventlist\r\n"
1616 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1617 "Supported: ms-piggyback-first-notify\r\n"
1618 "%sSupported: ms-benotify\r\n"
1619 "Proxy-Require: ms-benotify\r\n"
1620 "Event: presence\r\n"
1621 "Content-Type: %s\r\n"
1622 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1623 g_free(contact);
1625 /* subscribe to buddy presence */
1626 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1627 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1628 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1629 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1631 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1633 g_free(content);
1634 g_free(to);
1635 g_free(request);
1636 g_free(key);
1639 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1640 SIPE_UNUSED_PARAMETER void *unused)
1642 gchar *to = sip_uri_self(sip);
1643 gchar *resources_uri = g_strdup("");
1644 if (sip->ocs2007) {
1645 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1646 } else {
1647 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1649 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1652 struct presence_batched_routed {
1653 gchar *host;
1654 GSList *buddies;
1657 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1659 struct presence_batched_routed *data = payload;
1660 GSList *buddies = data->buddies;
1661 while (buddies) {
1662 g_free(buddies->data);
1663 buddies = buddies->next;
1665 g_slist_free(data->buddies);
1666 g_free(data->host);
1667 g_free(payload);
1670 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1672 struct presence_batched_routed *data = payload;
1673 GSList *buddies = data->buddies;
1674 gchar *resources_uri = g_strdup("");
1675 while (buddies) {
1676 gchar *tmp = resources_uri;
1677 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1678 g_free(tmp);
1679 buddies = buddies->next;
1681 sipe_subscribe_presence_batched_to(sip, resources_uri,
1682 g_strdup(data->host));
1686 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1687 * The user sends a single SUBSCRIBE request to the subscribed contact.
1688 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1692 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1695 gchar *key;
1696 gchar *to = sip_uri((char *)buddy_name);
1697 gchar *tmp = get_contact(sip);
1698 gchar *request;
1699 gchar *content = NULL;
1700 gchar *autoextend = "";
1701 gchar *content_type = "";
1702 struct sip_dialog *dialog;
1703 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, to);
1704 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
1706 if (sbuddy) sbuddy->just_added = FALSE;
1708 if (sip->ocs2007) {
1709 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1710 } else {
1711 autoextend = "Supported: com.microsoft.autoextend\r\n";
1714 request = g_strdup_printf(
1715 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1716 "Supported: ms-piggyback-first-notify\r\n"
1717 "%s%sSupported: ms-benotify\r\n"
1718 "Proxy-Require: ms-benotify\r\n"
1719 "Event: presence\r\n"
1720 "Contact: %s\r\n", autoextend, content_type, tmp);
1722 if (sip->ocs2007) {
1723 content = g_strdup_printf(
1724 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1725 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1726 "<resource uri=\"%s\"%s\n"
1727 "</adhocList>\n"
1728 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1729 "<category name=\"calendarData\"/>\n"
1730 "<category name=\"contactCard\"/>\n"
1731 "<category name=\"note\"/>\n"
1732 "<category name=\"state\"/>\n"
1733 "</categoryList>\n"
1734 "</action>\n"
1735 "</batchSub>", sip->username, to, context);
1738 g_free(tmp);
1740 /* subscribe to buddy presence */
1741 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1742 key = g_strdup_printf(ACTION_NAME_PRESENCE, to);
1743 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
1744 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
1746 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, dialog, process_subscribe_response);
1748 g_free(content);
1749 g_free(to);
1750 g_free(request);
1751 g_free(key);
1754 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1756 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status));
1758 if (!purple_status_is_active(status))
1759 return;
1761 if (account->gc) {
1762 struct sipe_account_data *sip = account->gc->proto_data;
1764 if (sip) {
1765 gchar *action_name;
1766 g_free(sip->status);
1767 sip->status = g_strdup(purple_status_get_id(status));
1769 /* schedule 2 sec to capture idle flag */
1770 action_name = g_strdup_printf("<%s>", "+set-status");
1771 sipe_schedule_action(action_name, 2, (Action)send_presence_status, NULL, sip, NULL);
1772 g_free(action_name);
1776 static void
1777 sipe_set_idle(PurpleConnection * gc,
1778 int time)
1780 purple_debug_info("sipe", "sipe_set_idle: time=%d\n", time);
1782 if (gc) {
1783 struct sipe_account_data *sip = gc->proto_data;
1785 if (sip) {
1786 sip->was_idle = sip->is_idle;
1787 sip->is_idle = (time > 0);
1792 static void
1793 sipe_alias_buddy(PurpleConnection *gc, const char *name,
1794 SIPE_UNUSED_PARAMETER const char *alias)
1796 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1797 sipe_group_set_user(sip, name);
1800 static void
1801 sipe_group_buddy(PurpleConnection *gc,
1802 const char *who,
1803 const char *old_group_name,
1804 const char *new_group_name)
1806 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1807 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1808 struct sipe_group * old_group = NULL;
1809 struct sipe_group * new_group;
1811 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1812 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1814 if(!buddy) { // buddy not in roaming list
1815 return;
1818 if (old_group_name) {
1819 old_group = sipe_group_find_by_name(sip, old_group_name);
1821 new_group = sipe_group_find_by_name(sip, new_group_name);
1823 if (old_group) {
1824 buddy->groups = g_slist_remove(buddy->groups, old_group);
1825 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1828 if (!new_group) {
1829 sipe_group_create(sip, new_group_name, who);
1830 } else {
1831 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1832 sipe_group_set_user(sip, who);
1836 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1838 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1840 /* libpurple can call us with undefined buddy or group */
1841 if (buddy && group) {
1842 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1844 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1845 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1846 purple_blist_rename_buddy(buddy, buddy_name);
1847 g_free(buddy_name);
1849 /* Prepend sip: if needed */
1850 if (strncmp("sip:", buddy->name, 4)) {
1851 gchar *buf = sip_uri_from_name(buddy->name);
1852 purple_blist_rename_buddy(buddy, buf);
1853 g_free(buf);
1856 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1857 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1858 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy->name);
1859 b->name = g_strdup(buddy->name);
1860 b->just_added = TRUE;
1861 g_hash_table_insert(sip->buddies, b->name, b);
1862 sipe_group_buddy(gc, b->name, NULL, group->name);
1863 /* @TODO should go to callback */
1864 sipe_subscribe_presence_single(sip, b->name);
1865 } else {
1866 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy->name);
1871 static void sipe_free_buddy(struct sipe_buddy *buddy)
1873 #ifndef _WIN32
1875 * We are calling g_hash_table_foreach_steal(). That means that no
1876 * key/value deallocation functions are called. Therefore the glib
1877 * hash code does not touch the key (buddy->name) or value (buddy)
1878 * of the to-be-deleted hash node at all. It follows that we
1880 * - MUST free the memory for the key ourselves and
1881 * - ARE allowed to do it in this function
1883 * Conclusion: glib must be broken on the Windows platform if sipe
1884 * crashes with SIGTRAP when closing. You'll have to live
1885 * with the memory leak until this is fixed.
1887 g_free(buddy->name);
1888 #endif
1889 g_free(buddy->activity);
1890 g_free(buddy->meeting_subject);
1891 g_free(buddy->meeting_location);
1892 g_free(buddy->annotation);
1893 g_free(buddy->device_name);
1894 g_slist_free(buddy->groups);
1895 g_free(buddy);
1899 * Unassociates buddy from group first.
1900 * Then see if no groups left, removes buddy completely.
1901 * Otherwise updates buddy groups on server.
1903 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1905 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1906 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1907 struct sipe_group *g = NULL;
1909 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1911 if (!b) return;
1913 if (group) {
1914 g = sipe_group_find_by_name(sip, group->name);
1917 if (g) {
1918 b->groups = g_slist_remove(b->groups, g);
1919 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1922 if (g_slist_length(b->groups) < 1) {
1923 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1924 sipe_cancel_scheduled_action(sip, action_name);
1925 g_free(action_name);
1927 g_hash_table_remove(sip->buddies, buddy->name);
1929 if (b->name) {
1930 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1931 send_soap_request(sip, body);
1932 g_free(body);
1935 sipe_free_buddy(b);
1936 } else {
1937 //updates groups on server
1938 sipe_group_set_user(sip, b->name);
1943 static void
1944 sipe_rename_group(PurpleConnection *gc,
1945 const char *old_name,
1946 PurpleGroup *group,
1947 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1949 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1950 struct sipe_group * s_group = sipe_group_find_by_name(sip, old_name);
1951 if (s_group) {
1952 sipe_group_rename(sip, s_group, group->name);
1953 } else {
1954 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1958 static void
1959 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1961 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1962 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1963 if (s_group) {
1964 gchar *body;
1965 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1966 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1967 send_soap_request(sip, body);
1968 g_free(body);
1970 sip->groups = g_slist_remove(sip->groups, s_group);
1971 g_free(s_group->name);
1972 g_free(s_group);
1973 } else {
1974 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1978 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
1980 PurpleStatusType *type;
1981 GList *types = NULL;
1983 /* Macros to reduce code repetition.
1984 Translators: noun */
1985 #define SIPE_ADD_STATUS(prim,id,name) type = purple_status_type_new_with_attrs( \
1986 prim, id, name, \
1987 TRUE, TRUE, FALSE, \
1988 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
1989 NULL); \
1990 types = g_list_append(types, type);
1991 #define SIPE_ADD_STATUS_NO_MSG(prim,id,name,user) type = purple_status_type_new( \
1992 prim, id, name, user); \
1993 types = g_list_append(types, type);
1995 /* Online */
1996 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE,
1997 NULL, NULL);
1999 /* Busy */
2000 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2001 SIPE_STATUS_ID_BUSY, _("Busy"));
2003 /* Do Not Disturb (not user settable) */
2004 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_UNAVAILABLE,
2005 SIPE_STATUS_ID_DND, NULL,
2006 FALSE);
2008 /* Be Right Back */
2009 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2010 SIPE_STATUS_ID_BRB, _("Be Right Back"));
2012 /* Away */
2013 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2014 NULL, NULL);
2016 /* On The Phone */
2017 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE,
2018 SIPE_STATUS_ID_ONPHONE, _("On The Phone"));
2020 /* Out To Lunch */
2021 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY,
2022 SIPE_STATUS_ID_LUNCH, _("Out To Lunch"));
2024 /* Idle/Inactive (not user settable) */
2025 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_AWAY,
2026 SIPE_STATUS_ID_IDLE, _("Inactive"),
2027 FALSE);
2029 /* Appear Offline */
2030 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_INVISIBLE,
2031 NULL, NULL,
2032 TRUE);
2034 /* Offline */
2035 SIPE_ADD_STATUS_NO_MSG(PURPLE_STATUS_OFFLINE,
2036 NULL, NULL,
2037 TRUE);
2039 return types;
2043 * A callback for g_hash_table_foreach
2045 static void sipe_buddy_subscribe_cb(SIPE_UNUSED_PARAMETER char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
2047 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
2048 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2049 guint time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2050 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
2051 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy->name));
2055 * Removes entries from purple buddy list
2056 * that does not correspond ones in the roaming contact list.
2058 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
2059 GSList *buddies = purple_find_buddies(sip->account, NULL);
2060 GSList *entry = buddies;
2061 struct sipe_buddy *buddy;
2062 PurpleBuddy *b;
2063 PurpleGroup *g;
2065 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
2066 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
2067 while (entry) {
2068 b = entry->data;
2069 g = purple_buddy_get_group(b);
2070 buddy = g_hash_table_lookup(sip->buddies, b->name);
2071 if(buddy) {
2072 gboolean in_sipe_groups = FALSE;
2073 GSList *entry2 = buddy->groups;
2074 while (entry2) {
2075 struct sipe_group *group = entry2->data;
2076 if (!strcmp(group->name, g->name)) {
2077 in_sipe_groups = TRUE;
2078 break;
2080 entry2 = entry2->next;
2082 if(!in_sipe_groups) {
2083 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
2084 purple_blist_remove_buddy(b);
2086 } else {
2087 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
2088 purple_blist_remove_buddy(b);
2090 entry = entry->next;
2092 g_slist_free(buddies);
2095 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
2097 int len = msg->bodylen;
2099 gchar *tmp = sipmsg_find_header(msg, "Event");
2100 xmlnode *item;
2101 xmlnode *isc;
2102 const gchar *contacts_delta;
2103 xmlnode *group_node;
2104 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2105 return FALSE;
2108 /* Convert the contact from XML to Purple Buddies */
2109 isc = xmlnode_from_str(msg->body, len);
2110 if (!isc) {
2111 return FALSE;
2114 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2115 if (contacts_delta) {
2116 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2119 if (!strcmp(isc->name, "contactList")) {
2121 /* Parse groups */
2122 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2123 struct sipe_group * group = g_new0(struct sipe_group, 1);
2124 const char *name = xmlnode_get_attrib(group_node, "name");
2126 if (!strncmp(name, "~", 1)) {
2127 name = _("Other Contacts");
2129 group->name = g_strdup(name);
2130 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2132 sipe_group_add(sip, group);
2135 // Make sure we have at least one group
2136 if (g_slist_length(sip->groups) == 0) {
2137 struct sipe_group * group = g_new0(struct sipe_group, 1);
2138 PurpleGroup *purple_group;
2139 group->name = g_strdup(_("Other Contacts"));
2140 group->id = 1;
2141 purple_group = purple_group_new(group->name);
2142 purple_blist_add_group(purple_group, NULL);
2143 sip->groups = g_slist_append(sip->groups, group);
2146 /* Parse contacts */
2147 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2148 const gchar *uri = xmlnode_get_attrib(item, "uri");
2149 const gchar *name = xmlnode_get_attrib(item, "name");
2150 gchar *buddy_name;
2151 struct sipe_buddy *buddy = NULL;
2152 gchar *tmp;
2153 gchar **item_groups;
2154 int i = 0;
2156 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2157 tmp = sip_uri_from_name(uri);
2158 buddy_name = g_ascii_strdown(tmp, -1);
2159 g_free(tmp);
2161 /* assign to group Other Contacts if nothing else received */
2162 tmp = g_strdup(xmlnode_get_attrib(item, "groups"));
2163 if(!tmp || !strcmp("", tmp) ) {
2164 struct sipe_group *group = sipe_group_find_by_name(sip, _("Other Contacts"));
2165 g_free(tmp);
2166 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
2168 item_groups = g_strsplit(tmp, " ", 0);
2169 g_free(tmp);
2171 while (item_groups[i]) {
2172 struct sipe_group *group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2174 // If couldn't find the right group for this contact, just put them in the first group we have
2175 if (group == NULL && g_slist_length(sip->groups) > 0) {
2176 group = sip->groups->data;
2179 if (group != NULL) {
2180 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2181 if (!b){
2182 b = purple_buddy_new(sip->account, buddy_name, uri);
2183 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2185 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name, uri);
2188 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2189 if (name != NULL && strlen(name) != 0) {
2190 purple_blist_alias_buddy(b, name);
2192 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name, name);
2196 if (!buddy) {
2197 buddy = g_new0(struct sipe_buddy, 1);
2198 buddy->name = g_strdup(b->name);
2199 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2202 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2204 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2205 } else {
2206 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2207 name);
2210 i++;
2211 } // while, contact groups
2212 g_strfreev(item_groups);
2213 g_free(buddy_name);
2215 } // for, contacts
2217 sipe_cleanup_local_blist(sip);
2219 xmlnode_free(isc);
2221 //subscribe to buddies
2222 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2223 if(sip->batched_support){
2224 sipe_subscribe_presence_batched(sip, NULL);
2226 else{
2227 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2229 sip->subscribed_buddies = TRUE;
2232 return 0;
2236 * Subscribe roaming contacts
2238 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2240 gchar *to = sip_uri_self(sip);
2241 gchar *tmp = get_contact(sip);
2242 gchar *hdr = g_strdup_printf(
2243 "Event: vnd-microsoft-roaming-contacts\r\n"
2244 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2245 "Supported: com.microsoft.autoextend\r\n"
2246 "Supported: ms-benotify\r\n"
2247 "Proxy-Require: ms-benotify\r\n"
2248 "Supported: ms-piggyback-first-notify\r\n"
2249 "Contact: %s\r\n", tmp);
2250 g_free(tmp);
2252 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2253 g_free(to);
2254 g_free(hdr);
2257 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2258 SIPE_UNUSED_PARAMETER void *unused)
2260 gchar *key;
2261 struct sip_dialog *dialog;
2262 gchar *to = sip_uri_self(sip);
2263 gchar *tmp = get_contact(sip);
2264 gchar *hdr = g_strdup_printf(
2265 "Event: presence.wpending\r\n"
2266 "Accept: text/xml+msrtc.wpending\r\n"
2267 "Supported: com.microsoft.autoextend\r\n"
2268 "Supported: ms-benotify\r\n"
2269 "Proxy-Require: ms-benotify\r\n"
2270 "Supported: ms-piggyback-first-notify\r\n"
2271 "Contact: %s\r\n", tmp);
2272 g_free(tmp);
2274 /* Subscription is identified by <event> key */
2275 key = g_strdup_printf("<%s>", "presence.wpending");
2276 dialog = (struct sip_dialog *)g_hash_table_lookup(sip->subscriptions, key);
2277 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key, dialog ? "Not NULL" : "NULL");
2279 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", dialog, process_subscribe_response);
2281 g_free(to);
2282 g_free(hdr);
2283 g_free(key);
2287 * Fires on deregistration event initiated by server.
2288 * [MS-SIPREGE] SIP extension.
2291 // 2007 Example
2293 // Content-Type: text/registration-event
2294 // subscription-state: terminated;expires=0
2295 // ms-diagnostics-public: 4141;reason="User disabled"
2297 // deregistered;event=rejected
2299 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2301 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2302 gchar *event = NULL;
2303 gchar *reason = NULL;
2304 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2306 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2307 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2309 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2310 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2311 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2312 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2313 } else {
2314 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2315 return;
2318 if (warning != NULL) {
2319 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2320 } else { // for LCS2005
2321 int error_id = 0;
2322 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2323 error_id = 4140; // [MS-SIPREGE]
2324 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2325 reason = g_strdup(_("you are already signed in at another location"));
2326 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2327 error_id = 4141;
2328 reason = g_strdup(_("user disabled")); // [MS-OCER]
2329 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2330 error_id = 4142;
2331 reason = g_strdup(_("user moved")); // [MS-OCER]
2334 g_free(event);
2335 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
2336 g_free(reason);
2338 sip->gc->wants_to_die = TRUE;
2339 purple_connection_error(sip->gc, warning);
2340 g_free(warning);
2344 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2346 xmlnode *xn_provision_group_list;
2347 xmlnode *node;
2349 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2351 /* provisionGroup */
2352 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2353 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2354 g_free(sip->focus_factory_uri);
2355 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2356 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2357 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2358 break;
2361 xmlnode_free(xn_provision_group_list);
2364 /** for 2005 system */
2365 static void
2366 sipe_process_provisioning(struct sipe_account_data *sip,
2367 struct sipmsg *msg)
2369 xmlnode *xn_provision;
2370 xmlnode *node;
2372 xn_provision = xmlnode_from_str(msg->body, msg->bodylen);
2373 if ((node = xmlnode_get_child(xn_provision, "user"))) {
2374 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node, "uri"));
2375 if ((node = xmlnode_get_child(node, "line"))) {
2376 const gchar *line_uri = xmlnode_get_attrib(node, "uri");
2377 const gchar *server = xmlnode_get_attrib(node, "server");
2378 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri, server);
2379 sip_csta_open(sip, line_uri, server);
2382 xmlnode_free(xn_provision);
2385 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2387 const gchar *contacts_delta;
2388 xmlnode *xml;
2390 xml = xmlnode_from_str(msg->body, msg->bodylen);
2391 if (!xml)
2393 return;
2396 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2397 if (contacts_delta)
2399 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2402 xmlnode_free(xml);
2405 static void
2406 free_container(struct sipe_container *container)
2408 GSList *entry;
2410 if (!container) return;
2412 entry = container->members;
2413 while (entry) {
2414 g_free(entry->data);
2415 entry = g_slist_remove(entry, entry->data);
2417 g_free(container);
2421 * Finds locally stored MS-PRES container member
2423 static struct sipe_container_member *
2424 sipe_find_container_member(struct sipe_container *container,
2425 const gchar *type,
2426 const gchar *value)
2428 struct sipe_container_member *member;
2429 GSList *entry;
2431 if (container == NULL || type == NULL) {
2432 return NULL;
2435 entry = container->members;
2436 while (entry) {
2437 member = entry->data;
2438 if (!g_strcasecmp(member->type, type)
2439 && ((!member->value && !value)
2440 || (value && member->value && !g_strcasecmp(member->value, value)))
2442 return member;
2444 entry = entry->next;
2446 return NULL;
2450 * Finds locally stored MS-PRES container by id
2452 static struct sipe_container *
2453 sipe_find_container(struct sipe_account_data *sip,
2454 guint id)
2456 struct sipe_container *container;
2457 GSList *entry;
2459 if (sip == NULL) {
2460 return NULL;
2463 entry = sip->containers;
2464 while (entry) {
2465 container = entry->data;
2466 if (id == container->id) {
2467 return container;
2469 entry = entry->next;
2471 return NULL;
2475 * Access Levels
2476 * 32000 - Blocked
2477 * 400 - Personal
2478 * 300 - Team
2479 * 200 - Company
2480 * 100 - Public
2482 static int
2483 sipe_find_access_level(struct sipe_account_data *sip,
2484 const gchar *type,
2485 const gchar *value)
2487 guint containers[] = {32000, 400, 300, 200, 100};
2488 int i = 0;
2490 for (i = 0; i < 5; i++) {
2491 struct sipe_container_member *member;
2492 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2493 if (!container) continue;
2495 member = sipe_find_container_member(container, type, value);
2496 if (member) {
2497 return containers[i];
2501 return -1;
2504 static void
2505 sipe_send_set_container_members(struct sipe_account_data *sip,
2506 guint container_id,
2507 guint container_version,
2508 const gchar* action,
2509 const gchar* type,
2510 const gchar* value)
2512 gchar *self = sip_uri_self(sip);
2513 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2514 gchar *contact;
2515 gchar *hdr;
2516 gchar *body = g_strdup_printf(
2517 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2518 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2519 "</setContainerMembers>",
2520 container_id,
2521 container_version,
2522 action,
2523 type,
2524 value_str);
2525 g_free(value_str);
2527 contact = get_contact(sip);
2528 hdr = g_strdup_printf("Contact: %s\r\n"
2529 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2530 g_free(contact);
2532 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2534 g_free(hdr);
2535 g_free(body);
2536 g_free(self);
2539 static void
2540 free_publication(struct sipe_publication *publication)
2542 g_free(publication->category);
2543 g_free(publication->note);
2544 g_free(publication);
2547 /* key is <category><instance><container> */
2548 static gboolean
2549 sipe_is_our_publication(struct sipe_account_data *sip,
2550 const gchar *key)
2552 GSList *entry;
2554 /* filling keys for our publications if not yet cached */
2555 if (!sip->our_publication_keys) {
2556 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2557 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2558 guint user_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER);
2560 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2561 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2563 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2564 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2565 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2566 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2568 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2569 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2570 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2571 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2573 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2574 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2575 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2576 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2577 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2578 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2580 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2581 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2584 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2586 entry = sip->our_publication_keys;
2587 while (entry) {
2588 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2589 if (!strcmp(entry->data, key)) {
2590 return TRUE;
2592 entry = entry->next;
2594 return FALSE;
2597 /** Property names to store in blist.xml */
2598 #define ALIAS_PROP "alias"
2599 #define EMAIL_PROP "email"
2600 #define PHONE_PROP "phone"
2601 #define PHONE_DISPLAY_PROP "phone-display"
2602 #define PHONE_MOBILE_PROP "phone-mobile"
2603 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2604 #define PHONE_HOME_PROP "phone-home"
2605 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2606 #define PHONE_OTHER_PROP "phone-other"
2607 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2608 #define PHONE_CUSTOM1_PROP "phone-custom1"
2609 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2610 #define SITE_PROP "site"
2611 #define COMPANY_PROP "company"
2612 #define DEPARTMENT_PROP "department"
2613 #define TITLE_PROP "title"
2614 #define OFFICE_PROP "office"
2615 /** implies work address */
2616 #define ADDRESS_STREET_PROP "address-street"
2617 #define ADDRESS_CITY_PROP "address-city"
2618 #define ADDRESS_STATE_PROP "address-state"
2619 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2620 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2622 * Update user information
2624 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2625 * @param property_name
2626 * @param property_value may be modified to strip white space
2628 static void
2629 sipe_update_user_info(struct sipe_account_data *sip,
2630 const char *uri,
2631 const char *property_name,
2632 char *property_value)
2634 GSList *buddies, *entry;
2636 if (!property_name || strlen(property_name) == 0) return;
2638 if (property_value)
2639 property_value = g_strstrip(property_value);
2641 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
2642 while (entry) {
2643 const char *prop_str;
2644 const char *server_alias;
2645 PurpleBuddy *p_buddy = entry->data;
2647 /* for Display Name */
2648 if (!strcmp(property_name, ALIAS_PROP)) {
2649 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
2650 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, property_value);
2651 purple_blist_alias_buddy(p_buddy, property_value);
2654 server_alias = purple_buddy_get_server_alias(p_buddy);
2655 if (property_value && strlen(property_value) > 0 &&
2656 ( (server_alias && strcmp(property_value, server_alias))
2657 || !server_alias || strlen(server_alias) == 0 )
2659 purple_blist_server_alias_buddy(p_buddy, property_value);
2662 /* for other properties */
2663 else {
2664 if (property_value && strlen(property_value) > 0) {
2665 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
2666 if (!prop_str || g_ascii_strcasecmp(prop_str, property_value)) {
2667 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
2672 entry = entry->next;
2674 g_slist_free(buddies);
2678 * Update user phone
2679 * Suitable for both 2005 and 2007 systems.
2681 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2682 * @param phone_type
2683 * @param phone may be modified to strip white space
2684 * @param phone_display_string may be modified to strip white space
2686 static void
2687 sipe_update_user_phone(struct sipe_account_data *sip,
2688 const char *uri,
2689 const gchar *phone_type,
2690 gchar *phone,
2691 gchar *phone_display_string)
2693 const char *phone_node = PHONE_PROP; /* work phone by default */
2694 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
2696 if(!phone || strlen(phone) == 0) return;
2698 if (phone_type && (!strcmp(phone_type, "mobile") || !strcmp(phone_type, "cell"))) {
2699 phone_node = PHONE_MOBILE_PROP;
2700 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
2701 } else if (phone_type && !strcmp(phone_type, "home")) {
2702 phone_node = PHONE_HOME_PROP;
2703 phone_display_node = PHONE_HOME_DISPLAY_PROP;
2704 } else if (phone_type && !strcmp(phone_type, "other")) {
2705 phone_node = PHONE_OTHER_PROP;
2706 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
2707 } else if (phone_type && !strcmp(phone_type, "custom1")) {
2708 phone_node = PHONE_CUSTOM1_PROP;
2709 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
2712 sipe_update_user_info(sip, uri, phone_node, phone);
2713 if (phone_display_string) {
2714 sipe_update_user_info(sip, uri, phone_display_node, phone_display_string);
2718 static void
2719 send_publish_category_initial(struct sipe_account_data *sip);
2722 * When we receive some self (BE) NOTIFY with a new subscriber
2723 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2726 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
2728 gchar *contact;
2729 gchar *to;
2730 xmlnode *xml;
2731 xmlnode *node;
2732 xmlnode *node2;
2733 char *display_name = NULL;
2734 char *uri;
2735 GSList *category_names = NULL;
2737 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2739 xml = xmlnode_from_str(msg->body, msg->bodylen);
2740 if (!xml) return;
2742 contact = get_contact(sip);
2743 to = sip_uri_self(sip);
2746 /* categories */
2747 /* set list of categories participating in this XML */
2748 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
2749 const gchar *name = xmlnode_get_attrib(node, "name");
2750 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2752 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
2753 category_names ? (int) g_slist_length(category_names) : -1);
2754 /* drop category information */
2755 if (category_names) {
2756 GSList *entry = category_names;
2757 while (entry) {
2758 GHashTable *cat_publications;
2759 const gchar *category = entry->data;
2760 entry = entry->next;
2761 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category);
2762 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2763 if (cat_publications) {
2764 g_hash_table_remove(sip->our_publications, category);
2765 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category);
2769 g_slist_free(category_names);
2770 /* filling our categories reflected in roaming data */
2771 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
2772 const gchar *name = xmlnode_get_attrib(node, "name");
2773 const gchar *container = xmlnode_get_attrib(node, "container");
2774 const gchar *instance = xmlnode_get_attrib(node, "instance");
2775 const gchar *version = xmlnode_get_attrib(node, "version");
2776 guint version_int = version ? atoi(version) : 0;
2777 gchar *key;
2779 if (!container || !instance) continue;
2781 /* key is <category><instance><container> */
2782 key = g_strdup_printf("<%s><%s><%s>", name, instance, container);
2783 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version_int);
2784 if (sipe_is_our_publication(sip, key)) {
2785 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2787 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2788 publication->category = g_strdup(name);
2789 publication->instance = atoi(instance);
2790 publication->container = atoi(container);
2791 publication->version = version_int;
2792 /* filling publication->availability */
2793 if (!strcmp(name, "state")) {
2794 xmlnode *xn_avail = xmlnode_get_descendant(node, "state", "availability", NULL);
2795 if (xn_avail) {
2796 gchar *avail_str = xmlnode_get_data(xn_avail);
2797 if (avail_str) {
2798 publication->availability = atoi(avail_str);
2800 g_free(avail_str);
2803 /* filling publication->note */
2804 if (!strcmp(name, "note")) {
2805 xmlnode *xn_body = xmlnode_get_descendant(node, "note", "body", NULL);
2806 if (xn_body) {
2807 publication->note = xmlnode_get_data(xn_body);
2811 if (!cat_publications) {
2812 cat_publications = g_hash_table_new_full(
2813 g_str_hash, g_str_equal,
2814 g_free, (GDestroyNotify)free_publication);
2815 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2816 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name);
2818 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2819 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version_int);
2821 g_free(key);
2823 /* userProperties published by server from AD */
2824 if (!sip->csta && !strcmp(name, "userProperties")) {
2825 xmlnode *line;
2826 /* line, for Remote Call Control (RCC) */
2827 for (line = xmlnode_get_descendant(node, "userProperties", "lines", "line", NULL); line; line = xmlnode_get_next_twin(line)) {
2828 const gchar *line_server = xmlnode_get_attrib(line, "lineServer");
2829 const gchar *line_type = xmlnode_get_attrib(line, "lineType");
2830 gchar *line_uri;
2832 if (!line_server || (strcmp(line_type, "Rcc") && strcmp(line_type, "Dual"))) continue;
2834 line_uri = xmlnode_get_data(line);
2835 if (line_uri) {
2836 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri, line_server);
2837 sip_csta_open(sip, line_uri, line_server);
2839 g_free(line_uri);
2841 break;
2845 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
2846 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2848 /* containers */
2849 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
2850 guint id = atoi(xmlnode_get_attrib(node, "id"));
2851 struct sipe_container *container = sipe_find_container(sip, id);
2853 if (container) {
2854 sip->containers = g_slist_remove(sip->containers, container);
2855 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
2856 free_container(container);
2858 container = g_new0(struct sipe_container, 1);
2859 container->id = id;
2860 container->version = atoi(xmlnode_get_attrib(node, "version"));
2861 sip->containers = g_slist_append(sip->containers, container);
2862 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
2864 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
2865 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2866 member->type = xmlnode_get_attrib(node2, "type");
2867 member->value = xmlnode_get_attrib(node2, "value");
2868 container->members = g_slist_append(container->members, member);
2869 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
2870 member->type, member->value ? member->value : "");
2874 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
2875 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
2876 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
2877 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
2878 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
2879 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
2880 /* initial set-up to let counterparties see your status */
2881 if (sameEnterpriseAL < 0) {
2882 struct sipe_container *container = sipe_find_container(sip, 200);
2883 guint version = container ? container->version : 0;
2884 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
2886 if (federatedAL < 0) {
2887 struct sipe_container *container = sipe_find_container(sip, 100);
2888 guint version = container ? container->version : 0;
2889 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
2891 sip->access_level_set = TRUE;
2894 /* subscribers */
2895 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2896 const char *user;
2897 const char *acknowledged;
2898 gchar *hdr;
2899 gchar *body;
2901 user = xmlnode_get_attrib(node, "user"); /* without 'sip:' prefix */
2902 if (!user) continue;
2903 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2904 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2905 uri = sip_uri_from_name(user);
2907 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
2909 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2910 if(!g_ascii_strcasecmp(acknowledged,"false")){
2911 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2912 if (!purple_find_buddy(sip->account, uri)) {
2913 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
2916 hdr = g_strdup_printf(
2917 "Contact: %s\r\n"
2918 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2920 body = g_strdup_printf(
2921 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2922 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2923 "</setSubscribers>", user);
2925 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2926 g_free(body);
2927 g_free(hdr);
2929 g_free(display_name);
2930 g_free(uri);
2933 g_free(to);
2934 g_free(contact);
2935 xmlnode_free(xml);
2937 /* Publish initial state if not yet.
2938 * Assuming this happens on initial responce to subscription to roaming-self
2939 * so we've already updated our roaming data in full.
2940 * Only for 2007+
2942 if (sip->ocs2007 && !sip->initial_state_published) {
2943 send_publish_category_initial(sip);
2944 sip->initial_state_published = TRUE;
2948 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
2950 gchar *to = sip_uri_self(sip);
2951 gchar *tmp = get_contact(sip);
2952 gchar *hdr = g_strdup_printf(
2953 "Event: vnd-microsoft-roaming-ACL\r\n"
2954 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2955 "Supported: com.microsoft.autoextend\r\n"
2956 "Supported: ms-benotify\r\n"
2957 "Proxy-Require: ms-benotify\r\n"
2958 "Supported: ms-piggyback-first-notify\r\n"
2959 "Contact: %s\r\n", tmp);
2960 g_free(tmp);
2962 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2963 g_free(to);
2964 g_free(hdr);
2968 * To request for presence information about the user, access level settings that have already been configured by the user
2969 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2970 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2973 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
2975 gchar *to = sip_uri_self(sip);
2976 gchar *tmp = get_contact(sip);
2977 gchar *hdr = g_strdup_printf(
2978 "Event: vnd-microsoft-roaming-self\r\n"
2979 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2980 "Supported: ms-benotify\r\n"
2981 "Proxy-Require: ms-benotify\r\n"
2982 "Supported: ms-piggyback-first-notify\r\n"
2983 "Contact: %s\r\n"
2984 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2986 gchar *body=g_strdup(
2987 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2988 "<roaming type=\"categories\"/>"
2989 "<roaming type=\"containers\"/>"
2990 "<roaming type=\"subscribers\"/></roamingList>");
2992 g_free(tmp);
2993 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2994 g_free(body);
2995 g_free(to);
2996 g_free(hdr);
3000 * For 2005 version
3002 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
3004 gchar *to = sip_uri_self(sip);
3005 gchar *tmp = get_contact(sip);
3006 gchar *hdr = g_strdup_printf(
3007 "Event: vnd-microsoft-provisioning\r\n"
3008 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3009 "Supported: com.microsoft.autoextend\r\n"
3010 "Supported: ms-benotify\r\n"
3011 "Proxy-Require: ms-benotify\r\n"
3012 "Supported: ms-piggyback-first-notify\r\n"
3013 "Expires: 0\r\n"
3014 "Contact: %s\r\n", tmp);
3016 g_free(tmp);
3017 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
3018 g_free(to);
3019 g_free(hdr);
3022 /** Subscription for provisioning information to help with initial
3023 * configuration. This subscription is a one-time query (denoted by the Expires header,
3024 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3025 * configuration, meeting policies, and policy settings that Communicator must enforce.
3026 * TODO: for what we need this information.
3029 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
3031 gchar *to = sip_uri_self(sip);
3032 gchar *tmp = get_contact(sip);
3033 gchar *hdr = g_strdup_printf(
3034 "Event: vnd-microsoft-provisioning-v2\r\n"
3035 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3036 "Supported: com.microsoft.autoextend\r\n"
3037 "Supported: ms-benotify\r\n"
3038 "Proxy-Require: ms-benotify\r\n"
3039 "Supported: ms-piggyback-first-notify\r\n"
3040 "Expires: 0\r\n"
3041 "Contact: %s\r\n"
3042 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
3043 gchar *body = g_strdup(
3044 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3045 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3046 "<provisioningGroup name=\"ucPolicy\"/>"
3047 "</provisioningGroupList>");
3049 g_free(tmp);
3050 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
3051 g_free(body);
3052 g_free(to);
3053 g_free(hdr);
3056 static void
3057 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
3058 gpointer value, gpointer user_data)
3060 struct sip_subscription *subscription = value;
3061 struct sip_dialog *dialog = &subscription->dialog;
3062 struct sipe_account_data *sip = user_data;
3063 gchar *tmp = get_contact(sip);
3064 gchar *hdr = g_strdup_printf(
3065 "Event: %s\r\n"
3066 "Expires: 0\r\n"
3067 "Contact: %s\r\n", subscription->event, tmp);
3068 g_free(tmp);
3070 /* Rate limit to max. 25 requests per seconds */
3071 g_usleep(1000000 / 25);
3073 send_sip_request(sip->gc, "SUBSCRIBE", dialog->with, dialog->with, hdr, NULL, dialog, NULL);
3074 g_free(hdr);
3077 /* IM Session (INVITE and MESSAGE methods) */
3079 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3080 static gchar *
3081 get_end_points (struct sipe_account_data *sip,
3082 struct sip_session *session)
3084 gchar *res;
3086 if (session == NULL) {
3087 return NULL;
3090 res = g_strdup_printf("<sip:%s>", sip->username);
3092 SIPE_DIALOG_FOREACH {
3093 gchar *tmp = res;
3094 res = g_strdup_printf("%s, <%s>", res, dialog->with);
3095 g_free(tmp);
3097 if (dialog->theirepid) {
3098 tmp = res;
3099 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
3100 g_free(tmp);
3102 } SIPE_DIALOG_FOREACH_END;
3104 return res;
3107 static gboolean
3108 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
3109 struct sipmsg *msg,
3110 SIPE_UNUSED_PARAMETER struct transaction *trans)
3112 gboolean ret = TRUE;
3114 if (msg->response != 200) {
3115 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
3116 return FALSE;
3119 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
3121 return ret;
3125 * Asks UA/proxy about its capabilities.
3127 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
3129 gchar *to = sip_uri(who);
3130 gchar *contact = get_contact(sip);
3131 gchar *request = g_strdup_printf(
3132 "Accept: application/sdp\r\n"
3133 "Contact: %s\r\n", contact);
3134 g_free(contact);
3136 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
3138 g_free(to);
3139 g_free(request);
3142 static void
3143 sipe_notify_user(struct sipe_account_data *sip,
3144 struct sip_session *session,
3145 PurpleMessageFlags flags,
3146 const gchar *message)
3148 PurpleConversation *conv;
3150 if (!session->conv) {
3151 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
3152 } else {
3153 conv = session->conv;
3155 purple_conversation_write(conv, NULL, message, flags, time(NULL));
3158 void
3159 sipe_present_info(struct sipe_account_data *sip,
3160 struct sip_session *session,
3161 const gchar *message)
3163 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
3166 static void
3167 sipe_present_err(struct sipe_account_data *sip,
3168 struct sip_session *session,
3169 const gchar *message)
3171 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
3174 void
3175 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
3176 struct sip_session *session,
3177 int sip_error,
3178 const gchar *who,
3179 const gchar *message)
3181 char *msg, *msg_tmp, *msg_tmp2;
3182 const char *label;
3184 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
3185 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
3186 g_free(msg_tmp);
3187 /* Service unavailable; Server Internal Error; Server Time-out */
3188 if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3189 label = _("This message was not delivered to %s because the service is not available");
3190 } else if (sip_error == 486) { /* Busy Here */
3191 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3192 } else {
3193 label = _("This message was not delivered to %s because one or more recipients are offline");
3196 msg_tmp = g_strdup_printf( "%s:\n%s" ,
3197 msg_tmp2 = g_strdup_printf(label, who ? who : ""), msg ? msg : "");
3198 sipe_present_err(sip, session, msg_tmp);
3199 g_free(msg_tmp2);
3200 g_free(msg_tmp);
3201 g_free(msg);
3205 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
3207 static gboolean
3208 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
3209 SIPE_UNUSED_PARAMETER struct transaction *trans)
3211 gboolean ret = TRUE;
3212 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3213 struct sip_session *session = sipe_session_find_im(sip, with);
3214 struct sip_dialog *dialog;
3215 gchar *cseq;
3216 char *key;
3217 gchar *message;
3219 if (!session) {
3220 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3221 g_free(with);
3222 return FALSE;
3225 dialog = sipe_dialog_find(session, with);
3226 if (!dialog) {
3227 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3228 g_free(with);
3229 return FALSE;
3232 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3233 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3234 g_free(cseq);
3235 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3237 if (msg->response >= 400) {
3238 PurpleBuddy *pbuddy;
3239 gchar *alias = with;
3241 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3243 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3244 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3247 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3248 ret = FALSE;
3249 } else {
3250 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3251 if (message_id) {
3252 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
3253 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3254 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3257 g_hash_table_remove(session->unconfirmed_messages, key);
3258 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3259 key, g_hash_table_size(session->unconfirmed_messages));
3262 g_free(key);
3263 g_free(with);
3265 if (ret) sipe_im_process_queue(sip, session);
3266 return ret;
3269 static gboolean
3270 sipe_is_election_finished(struct sip_session *session);
3272 static void
3273 sipe_election_result(struct sipe_account_data *sip,
3274 void *sess);
3276 static gboolean
3277 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
3278 SIPE_UNUSED_PARAMETER struct transaction *trans)
3280 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3281 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3282 struct sip_dialog *dialog;
3283 struct sip_session *session;
3285 session = sipe_session_find_chat_by_callid(sip, callid);
3286 if (!session) {
3287 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
3288 return FALSE;
3291 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
3292 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3293 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
3294 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
3296 if (xn_request_rm_response) {
3297 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
3298 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
3300 dialog = sipe_dialog_find(session, with);
3301 if (!dialog) {
3302 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
3303 return FALSE;
3306 if (allow && !g_strcasecmp(allow, "true")) {
3307 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
3308 dialog->election_vote = 1;
3309 } else if (allow && !g_strcasecmp(allow, "false")) {
3310 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
3311 dialog->election_vote = -1;
3314 if (sipe_is_election_finished(session)) {
3315 sipe_election_result(sip, session);
3318 } else if (xn_set_rm_response) {
3321 xmlnode_free(xn_action);
3325 return TRUE;
3328 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
3330 gchar *hdr;
3331 gchar *tmp;
3332 char *msgformat;
3333 char *msgtext;
3334 gchar *msgr_value;
3335 gchar *msgr;
3337 sipe_parse_html(msg, &msgformat, &msgtext);
3338 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat);
3340 msgr_value = sipmsg_get_msgr_string(msgformat);
3341 g_free(msgformat);
3342 if (msgr_value) {
3343 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3344 g_free(msgr_value);
3345 } else {
3346 msgr = g_strdup("");
3349 tmp = get_contact(sip);
3350 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3351 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3352 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3353 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
3354 g_free(tmp);
3355 g_free(msgr);
3357 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
3358 g_free(msgtext);
3359 g_free(hdr);
3363 static void
3364 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
3366 GSList *entry2 = session->outgoing_message_queue;
3367 while (entry2) {
3368 char *queued_msg = entry2->data;
3370 /* for multiparty chat or conference */
3371 if (session->is_multiparty || session->focus_uri) {
3372 gchar *who = sip_uri_self(sip);
3373 serv_got_chat_in(sip->gc, session->chat_id, who,
3374 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
3375 g_free(who);
3378 SIPE_DIALOG_FOREACH {
3379 char *key;
3381 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3383 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3384 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
3385 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
3386 key, g_hash_table_size(session->unconfirmed_messages));
3387 g_free(key);
3389 sipe_send_message(sip, dialog, queued_msg);
3390 } SIPE_DIALOG_FOREACH_END;
3392 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3393 g_free(queued_msg);
3397 static void
3398 sipe_refer_notify(struct sipe_account_data *sip,
3399 struct sip_session *session,
3400 const gchar *who,
3401 int status,
3402 const gchar *desc)
3404 gchar *hdr;
3405 gchar *body;
3406 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3408 hdr = g_strdup_printf(
3409 "Event: refer\r\n"
3410 "Subscription-State: %s\r\n"
3411 "Content-Type: message/sipfrag\r\n",
3412 status >= 200 ? "terminated" : "active");
3414 body = g_strdup_printf(
3415 "SIP/2.0 %d %s\r\n",
3416 status, desc);
3418 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
3420 g_free(hdr);
3421 g_free(body);
3424 static gboolean
3425 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
3427 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3428 struct sip_session *session;
3429 struct sip_dialog *dialog;
3430 char *cseq;
3431 char *key;
3432 gchar *message;
3433 struct sipmsg *request_msg = trans->msg;
3435 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3436 gchar *referred_by;
3438 session = sipe_session_find_chat_by_callid(sip, callid);
3439 if (!session) {
3440 session = sipe_session_find_im(sip, with);
3442 if (!session) {
3443 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
3444 g_free(with);
3445 return FALSE;
3448 dialog = sipe_dialog_find(session, with);
3449 if (!dialog) {
3450 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
3451 g_free(with);
3452 return FALSE;
3455 sipe_dialog_parse(dialog, msg, TRUE);
3457 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3458 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3459 g_free(cseq);
3460 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3462 if (msg->response != 200) {
3463 PurpleBuddy *pbuddy;
3464 gchar *alias = with;
3466 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
3468 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3469 alias = (gchar *)purple_buddy_get_alias(pbuddy);
3472 if (message) {
3473 sipe_present_message_undelivered_err(sip, session, msg->response, alias, message);
3474 } else {
3475 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3476 sipe_present_err(sip, session, tmp_msg);
3477 g_free(tmp_msg);
3480 sipe_dialog_remove(session, with);
3482 g_free(key);
3483 g_free(with);
3484 return FALSE;
3487 dialog->cseq = 0;
3488 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3489 dialog->outgoing_invite = NULL;
3490 dialog->is_established = TRUE;
3492 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3493 if (referred_by) {
3494 sipe_refer_notify(sip, session, referred_by, 200, "OK");
3495 g_free(referred_by);
3498 /* add user to chat if it is a multiparty session */
3499 if (session->is_multiparty) {
3500 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3501 with, NULL,
3502 PURPLE_CBFLAGS_NONE, TRUE);
3505 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3506 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3507 if (session->outgoing_message_queue) {
3508 char *queued_msg = session->outgoing_message_queue->data;
3509 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3510 g_free(queued_msg);
3514 sipe_im_process_queue(sip, session);
3516 g_hash_table_remove(session->unconfirmed_messages, key);
3517 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3518 key, g_hash_table_size(session->unconfirmed_messages));
3520 g_free(key);
3521 g_free(with);
3522 return TRUE;
3526 void
3527 sipe_invite(struct sipe_account_data *sip,
3528 struct sip_session *session,
3529 const gchar *who,
3530 const gchar *msg_body,
3531 const gchar *referred_by,
3532 const gboolean is_triggered)
3534 gchar *hdr;
3535 gchar *to;
3536 gchar *contact;
3537 gchar *body;
3538 gchar *self;
3539 char *ms_text_format = NULL;
3540 gchar *roster_manager;
3541 gchar *end_points;
3542 gchar *referred_by_str;
3543 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3545 if (dialog && dialog->is_established) {
3546 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
3547 return;
3550 if (!dialog) {
3551 dialog = sipe_dialog_add(session);
3552 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3553 dialog->with = g_strdup(who);
3556 if (!(dialog->ourtag)) {
3557 dialog->ourtag = gentag();
3560 to = sip_uri(who);
3562 if (msg_body) {
3563 char *msgformat;
3564 char *msgtext;
3565 char *base64_msg;
3566 gchar *msgr_value;
3567 gchar *msgr;
3568 char *key;
3570 sipe_parse_html(msg_body, &msgformat, &msgtext);
3571 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
3573 msgr_value = sipmsg_get_msgr_string(msgformat);
3574 g_free(msgformat);
3575 msgr = "";
3576 if (msgr_value) {
3577 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3578 g_free(msgr_value);
3581 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3582 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3583 g_free(msgtext);
3584 g_free(msgr);
3585 g_free(base64_msg);
3587 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3588 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3589 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
3590 key, g_hash_table_size(session->unconfirmed_messages));
3591 g_free(key);
3594 contact = get_contact(sip);
3595 end_points = get_end_points(sip, session);
3596 self = sip_uri_self(sip);
3597 roster_manager = g_strdup_printf(
3598 "Roster-Manager: %s\r\n"
3599 "EndPoints: %s\r\n",
3600 self,
3601 end_points);
3602 referred_by_str = referred_by ?
3603 g_strdup_printf(
3604 "Referred-By: %s\r\n",
3605 referred_by)
3606 : g_strdup("");
3607 hdr = g_strdup_printf(
3608 "Supported: ms-sender\r\n"
3609 "%s"
3610 "%s"
3611 "%s"
3612 "%s"
3613 "Contact: %s\r\n%s"
3614 "Content-Type: application/sdp\r\n",
3615 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3616 referred_by_str,
3617 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3618 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3619 contact,
3620 ms_text_format ? ms_text_format : "");
3621 g_free(ms_text_format);
3622 g_free(self);
3624 body = g_strdup_printf(
3625 "v=0\r\n"
3626 "o=- 0 0 IN IP4 %s\r\n"
3627 "s=session\r\n"
3628 "c=IN IP4 %s\r\n"
3629 "t=0 0\r\n"
3630 "m=message %d sip null\r\n"
3631 "a=accept-types:text/plain text/html image/gif "
3632 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3633 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3635 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3636 to, to, hdr, body, dialog, process_invite_response);
3638 g_free(to);
3639 g_free(roster_manager);
3640 g_free(end_points);
3641 g_free(referred_by_str);
3642 g_free(body);
3643 g_free(hdr);
3644 g_free(contact);
3647 static void
3648 sipe_refer(struct sipe_account_data *sip,
3649 struct sip_session *session,
3650 const gchar *who)
3652 gchar *hdr;
3653 gchar *contact;
3654 struct sip_dialog *dialog = sipe_dialog_find(session,
3655 session->roster_manager);
3657 contact = get_contact(sip);
3658 hdr = g_strdup_printf(
3659 "Contact: %s\r\n"
3660 "Refer-to: <%s>\r\n"
3661 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3662 "Require: com.microsoft.rtc-multiparty\r\n",
3663 contact,
3664 who,
3665 sip->username,
3666 dialog->ourtag ? ";tag=" : "",
3667 dialog->ourtag ? dialog->ourtag : "",
3668 get_epid(sip));
3670 send_sip_request(sip->gc, "REFER",
3671 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3673 g_free(hdr);
3674 g_free(contact);
3677 static void
3678 sipe_send_election_request_rm(struct sipe_account_data *sip,
3679 struct sip_dialog *dialog,
3680 int bid)
3682 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3684 gchar *body = g_strdup_printf(
3685 "<?xml version=\"1.0\"?>\r\n"
3686 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3687 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3688 sip->username, bid);
3690 send_sip_request(sip->gc, "INFO",
3691 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3693 g_free(body);
3696 static void
3697 sipe_send_election_set_rm(struct sipe_account_data *sip,
3698 struct sip_dialog *dialog)
3700 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3702 gchar *body = g_strdup_printf(
3703 "<?xml version=\"1.0\"?>\r\n"
3704 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3705 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3706 sip->username);
3708 send_sip_request(sip->gc, "INFO",
3709 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3711 g_free(body);
3714 static void
3715 sipe_session_close(struct sipe_account_data *sip,
3716 struct sip_session * session)
3718 if (session && session->focus_uri) {
3719 sipe_conf_immcu_closed(sip, session);
3720 conf_session_close(sip, session);
3723 if (session) {
3724 SIPE_DIALOG_FOREACH {
3725 /* @TODO slow down BYE message sending rate */
3726 /* @see single subscription code */
3727 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3728 } SIPE_DIALOG_FOREACH_END;
3730 sipe_session_remove(sip, session);
3734 static void
3735 sipe_session_close_all(struct sipe_account_data *sip)
3737 GSList *entry;
3738 while ((entry = sip->sessions) != NULL) {
3739 sipe_session_close(sip, entry->data);
3743 static void
3744 sipe_convo_closed(PurpleConnection * gc, const char *who)
3746 struct sipe_account_data *sip = gc->proto_data;
3748 purple_debug_info("sipe", "conversation with %s closed\n", who);
3749 sipe_session_close(sip, sipe_session_find_im(sip, who));
3752 static void
3753 sipe_chat_leave (PurpleConnection *gc, int id)
3755 struct sipe_account_data *sip = gc->proto_data;
3756 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
3758 sipe_session_close(sip, session);
3761 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3762 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3764 struct sipe_account_data *sip = gc->proto_data;
3765 struct sip_session *session;
3766 struct sip_dialog *dialog;
3768 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3770 session = sipe_session_find_or_add_im(sip, who);
3771 dialog = sipe_dialog_find(session, who);
3773 // Queue the message
3774 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3776 if (dialog && !dialog->outgoing_invite) {
3777 sipe_im_process_queue(sip, session);
3778 } else if (!dialog || !dialog->outgoing_invite) {
3779 // Need to send the INVITE to get the outgoing dialog setup
3780 sipe_invite(sip, session, who, what, NULL, FALSE);
3783 return 1;
3786 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
3787 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3789 struct sipe_account_data *sip = gc->proto_data;
3790 struct sip_session *session;
3792 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3794 session = sipe_session_find_chat_by_id(sip, id);
3796 // Queue the message
3797 if (session && session->dialogs) {
3798 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3799 g_strdup(what));
3800 sipe_im_process_queue(sip, session);
3801 } else if (sip) {
3802 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
3803 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
3805 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name ? chat_name : "NULL");
3806 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id ? proto_chat_id : "NULL");
3808 if (sip->ocs2007) {
3809 struct sip_session *session = sipe_session_add_chat(sip);
3811 session->is_multiparty = FALSE;
3812 session->focus_uri = g_strdup(proto_chat_id);
3813 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3814 g_strdup(what));
3815 sipe_invite_conf_focus(sip, session);
3819 return 1;
3822 /* End IM Session (INVITE and MESSAGE methods) */
3824 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3826 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3827 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3828 gchar *from;
3829 struct sip_session *session;
3831 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
3833 /* Call Control protocol */
3834 if (g_str_has_prefix(contenttype, "application/csta+xml"))
3836 process_incoming_info_csta(sip, msg);
3837 return;
3840 from = parse_from(sipmsg_find_header(msg, "From"));
3841 session = sipe_session_find_chat_by_callid(sip, callid);
3842 if (!session) {
3843 session = sipe_session_find_im(sip, from);
3845 if (!session) {
3846 g_free(from);
3847 return;
3850 if (g_str_has_prefix(contenttype, "application/x-ms-mim"))
3852 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3853 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3854 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3856 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
3858 if (xn_request_rm) {
3859 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3860 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3861 gchar *body = g_strdup_printf(
3862 "<?xml version=\"1.0\"?>\r\n"
3863 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3864 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3865 sip->username,
3866 session->bid < bid ? "true" : "false");
3867 send_sip_response(sip->gc, msg, 200, "OK", body);
3868 g_free(body);
3869 } else if (xn_set_rm) {
3870 gchar *body;
3871 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3872 g_free(session->roster_manager);
3873 session->roster_manager = g_strdup(rm);
3875 body = g_strdup_printf(
3876 "<?xml version=\"1.0\"?>\r\n"
3877 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3878 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3879 sip->username);
3880 send_sip_response(sip->gc, msg, 200, "OK", body);
3881 g_free(body);
3883 xmlnode_free(xn_action);
3886 else
3888 /* looks like purple lacks typing notification for chat */
3889 if (!session->is_multiparty && !session->focus_uri) {
3890 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
3891 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
3892 "status");
3893 if (status && !strcmp(status, "type")) {
3894 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3895 } else if (status && !strcmp(status, "idle")) {
3896 serv_got_typing_stopped(sip->gc, from);
3898 xmlnode_free(xn_keyboard_activity);
3901 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3903 g_free(from);
3906 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3908 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3909 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3910 struct sip_session *session;
3911 struct sip_dialog *dialog;
3913 /* collect dialog identification
3914 * we need callid, ourtag and theirtag to unambiguously identify dialog
3916 /* take data before 'msg' will be modified by send_sip_response */
3917 dialog = g_new0(struct sip_dialog, 1);
3918 dialog->callid = g_strdup(callid);
3919 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
3920 dialog->with = g_strdup(from);
3921 sipe_dialog_parse(dialog, msg, FALSE);
3923 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3925 session = sipe_session_find_chat_by_callid(sip, callid);
3926 if (!session) {
3927 session = sipe_session_find_im(sip, from);
3929 if (!session) {
3930 g_free(from);
3931 return;
3934 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3935 g_free(session->roster_manager);
3936 session->roster_manager = NULL;
3939 /* This what BYE is essentially for - terminating dialog */
3940 sipe_dialog_remove_3(session, dialog);
3941 sipe_dialog_free(dialog);
3942 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
3943 sipe_conf_immcu_closed(sip, session);
3944 } else if (session->is_multiparty) {
3945 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3948 g_free(from);
3951 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3953 gchar *self = sip_uri_self(sip);
3954 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3955 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3956 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3957 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3958 struct sip_session *session;
3959 struct sip_dialog *dialog;
3961 session = sipe_session_find_chat_by_callid(sip, callid);
3962 dialog = sipe_dialog_find(session, from);
3964 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3965 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3966 } else {
3967 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3969 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3972 g_free(self);
3973 g_free(from);
3974 g_free(refer_to);
3975 g_free(referred_by);
3978 static unsigned int
3979 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3981 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3982 struct sip_session *session;
3983 struct sip_dialog *dialog;
3985 if (state == PURPLE_NOT_TYPING)
3986 return 0;
3988 session = sipe_session_find_im(sip, who);
3989 dialog = sipe_dialog_find(session, who);
3991 if (session && dialog && dialog->is_established) {
3992 send_sip_request(gc, "INFO", who, who,
3993 "Content-Type: application/xml\r\n",
3994 SIPE_SEND_TYPING, dialog, NULL);
3996 return SIPE_TYPING_SEND_TIMEOUT;
3999 static gboolean resend_timeout(struct sipe_account_data *sip)
4001 GSList *tmp = sip->transactions;
4002 time_t currtime = time(NULL);
4003 while (tmp) {
4004 struct transaction *trans = tmp->data;
4005 tmp = tmp->next;
4006 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
4007 if ((currtime - trans->time > 5) && trans->retries >= 1) {
4008 /* TODO 408 */
4009 } else {
4010 if ((currtime - trans->time > 2) && trans->retries == 0) {
4011 trans->retries++;
4012 sendout_sipmsg(sip, trans->msg);
4016 return TRUE;
4019 static void do_reauthenticate_cb(struct sipe_account_data *sip,
4020 SIPE_UNUSED_PARAMETER void *unused)
4022 /* register again when security token expires */
4023 /* we have to start a new authentication as the security token
4024 * is almost expired by sending a not signed REGISTER message */
4025 purple_debug_info("sipe", "do a full reauthentication\n");
4026 sipe_auth_free(&sip->registrar);
4027 sipe_auth_free(&sip->proxy);
4028 sip->registerstatus = 0;
4029 do_register(sip);
4030 sip->reauthenticate_set = FALSE;
4033 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
4035 gchar *from;
4036 gchar *contenttype;
4037 gboolean found = FALSE;
4039 from = parse_from(sipmsg_find_header(msg, "From"));
4041 if (!from) return;
4043 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
4045 contenttype = sipmsg_find_header(msg, "Content-Type");
4046 if (!strncmp(contenttype, "text/plain", 10)
4047 || !strncmp(contenttype, "text/html", 9)
4048 || !strncmp(contenttype, "multipart/related", 21))
4050 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4051 gchar *html = get_html_message(contenttype, msg->body);
4053 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
4054 if (!session) {
4055 session = sipe_session_find_im(sip, from);
4058 if (session && session->focus_uri) { /* a conference */
4059 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
4060 gchar *sender = parse_from(tmp);
4061 g_free(tmp);
4062 serv_got_chat_in(sip->gc, session->chat_id, sender,
4063 PURPLE_MESSAGE_RECV, html, time(NULL));
4064 g_free(sender);
4065 } else if (session && session->is_multiparty) { /* a multiparty chat */
4066 serv_got_chat_in(sip->gc, session->chat_id, from,
4067 PURPLE_MESSAGE_RECV, html, time(NULL));
4068 } else {
4069 serv_got_im(sip->gc, from, html, 0, time(NULL));
4071 g_free(html);
4072 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4073 found = TRUE;
4075 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
4076 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
4077 xmlnode *state;
4078 gchar *statedata;
4080 if (!isc) {
4081 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4082 return;
4085 state = xmlnode_get_child(isc, "state");
4087 if (!state) {
4088 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4089 xmlnode_free(isc);
4090 return;
4093 statedata = xmlnode_get_data(state);
4094 if (statedata) {
4095 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
4096 else serv_got_typing_stopped(sip->gc, from);
4098 g_free(statedata);
4100 xmlnode_free(isc);
4101 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4102 found = TRUE;
4104 if (!found) {
4105 purple_debug_info("sipe", "got unknown mime-type");
4106 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
4108 g_free(from);
4111 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
4113 gchar *body;
4114 gchar *newTag;
4115 gchar *oldHeader;
4116 gchar *newHeader;
4117 gboolean is_multiparty = FALSE;
4118 gboolean is_triggered = FALSE;
4119 gboolean was_multiparty = TRUE;
4120 gboolean just_joined = FALSE;
4121 gchar *from;
4122 gchar *callid = sipmsg_find_header(msg, "Call-ID");
4123 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
4124 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
4125 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
4126 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4127 GSList *end_points = NULL;
4128 char *tmp = NULL;
4129 struct sip_session *session;
4131 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? tmp = fix_newlines(msg->body) : "");
4132 g_free(tmp);
4134 /* Invitation to join conference */
4135 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
4136 process_incoming_invite_conf(sip, msg);
4137 return;
4140 /* Only accept text invitations */
4141 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
4142 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4143 return;
4146 // TODO There *must* be a better way to clean up the To header to add a tag...
4147 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4148 oldHeader = sipmsg_find_header(msg, "To");
4149 newTag = gentag();
4150 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
4151 sipmsg_remove_header_now(msg, "To");
4152 sipmsg_add_header_now(msg, "To", newHeader);
4153 g_free(newHeader);
4155 if (end_points_hdr) {
4156 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
4158 if (g_slist_length(end_points) > 2) {
4159 is_multiparty = TRUE;
4162 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
4163 is_triggered = TRUE;
4164 is_multiparty = TRUE;
4167 session = sipe_session_find_chat_by_callid(sip, callid);
4168 /* Convert to multiparty */
4169 if (session && is_multiparty && !session->is_multiparty) {
4170 g_free(session->with);
4171 session->with = NULL;
4172 was_multiparty = FALSE;
4173 session->is_multiparty = TRUE;
4174 session->chat_id = rand();
4177 if (!session && is_multiparty) {
4178 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
4180 /* IM session */
4181 from = parse_from(sipmsg_find_header(msg, "From"));
4182 if (!session) {
4183 session = sipe_session_find_or_add_im(sip, from);
4186 g_free(session->callid);
4187 session->callid = g_strdup(callid);
4189 session->is_multiparty = is_multiparty;
4190 if (roster_manager) {
4191 session->roster_manager = g_strdup(roster_manager);
4194 if (is_multiparty && end_points) {
4195 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
4196 GSList *entry = end_points;
4197 while (entry) {
4198 struct sip_dialog *dialog;
4199 struct sipendpoint *end_point = entry->data;
4200 entry = entry->next;
4202 if (!g_strcasecmp(from, end_point->contact) ||
4203 !g_strcasecmp(to, end_point->contact))
4204 continue;
4206 dialog = sipe_dialog_find(session, end_point->contact);
4207 if (dialog) {
4208 g_free(dialog->theirepid);
4209 dialog->theirepid = end_point->epid;
4210 end_point->epid = NULL;
4211 } else {
4212 dialog = sipe_dialog_add(session);
4214 dialog->callid = g_strdup(session->callid);
4215 dialog->with = end_point->contact;
4216 end_point->contact = NULL;
4217 dialog->theirepid = end_point->epid;
4218 end_point->epid = NULL;
4220 just_joined = TRUE;
4222 /* send triggered INVITE */
4223 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
4226 g_free(to);
4229 if (end_points) {
4230 GSList *entry = end_points;
4231 while (entry) {
4232 struct sipendpoint *end_point = entry->data;
4233 entry = entry->next;
4234 g_free(end_point->contact);
4235 g_free(end_point->epid);
4236 g_free(end_point);
4238 g_slist_free(end_points);
4241 if (session) {
4242 struct sip_dialog *dialog = sipe_dialog_find(session, from);
4243 if (dialog) {
4244 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4245 } else {
4246 dialog = sipe_dialog_add(session);
4248 dialog->callid = g_strdup(session->callid);
4249 dialog->with = g_strdup(from);
4250 sipe_dialog_parse(dialog, msg, FALSE);
4252 if (!dialog->ourtag) {
4253 dialog->ourtag = newTag;
4254 newTag = NULL;
4257 just_joined = TRUE;
4259 } else {
4260 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4262 g_free(newTag);
4264 if (is_multiparty && !session->conv) {
4265 gchar *chat_title = sipe_chat_get_name(callid);
4266 gchar *self = sip_uri_self(sip);
4267 /* create prpl chat */
4268 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
4269 session->chat_title = g_strdup(chat_title);
4270 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
4271 /* add self */
4272 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4273 self, NULL,
4274 PURPLE_CBFLAGS_NONE, FALSE);
4275 g_free(chat_title);
4276 g_free(self);
4279 if (is_multiparty && !was_multiparty) {
4280 /* add current IM counterparty to chat */
4281 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4282 sipe_dialog_first(session)->with, NULL,
4283 PURPLE_CBFLAGS_NONE, FALSE);
4286 /* add inviting party to chat */
4287 if (just_joined && session->conv) {
4288 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
4289 from, NULL,
4290 PURPLE_CBFLAGS_NONE, TRUE);
4293 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4295 /* This used only in 2005 official client, not 2007 or Reuters.
4296 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4297 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4299 if (is_multiparty) {
4300 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4301 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
4302 if (ms_text_format) {
4303 if (g_str_has_prefix(ms_text_format, "text/plain") || g_str_has_prefix(ms_text_format, "text/html")) {
4305 gchar *html = get_html_message(ms_text_format, NULL);
4306 if (html) {
4307 if (is_multiparty) {
4308 serv_got_chat_in(sip->gc, session->chat_id, from,
4309 PURPLE_MESSAGE_RECV, html, time(NULL));
4310 } else {
4311 serv_got_im(sip->gc, from, html, 0, time(NULL));
4313 g_free(html);
4314 sipmsg_add_header(msg, "Supported", "ms-text-format"); /* accepts received message */
4321 g_free(from);
4323 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
4324 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4325 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4327 body = g_strdup_printf(
4328 "v=0\r\n"
4329 "o=- 0 0 IN IP4 %s\r\n"
4330 "s=session\r\n"
4331 "c=IN IP4 %s\r\n"
4332 "t=0 0\r\n"
4333 "m=message %d sip sip:%s\r\n"
4334 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4335 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
4336 sip->realport, sip->username);
4337 send_sip_response(sip->gc, msg, 200, "OK", body);
4338 g_free(body);
4341 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
4343 gchar *body;
4345 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4346 sipmsg_add_header(msg, "User-Agent", sipe_get_useragent(sip));
4347 sipmsg_add_header(msg, "Content-Type", "application/sdp");
4349 body = g_strdup_printf(
4350 "v=0\r\n"
4351 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4352 "s=session\r\n"
4353 "c=IN IP4 0.0.0.0\r\n"
4354 "t=0 0\r\n"
4355 "m=message %d sip sip:%s\r\n"
4356 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4357 sip->realport, sip->username);
4358 send_sip_response(sip->gc, msg, 200, "OK", body);
4359 g_free(body);
4362 static void sipe_connection_cleanup(struct sipe_account_data *);
4363 static void create_connection(struct sipe_account_data *, gchar *, int);
4365 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg,
4366 SIPE_UNUSED_PARAMETER struct transaction *trans)
4368 gchar *tmp;
4369 const gchar *expires_header;
4370 int expires, i;
4371 GSList *hdr = msg->headers;
4372 struct siphdrelement *elem;
4374 expires_header = sipmsg_find_header(msg, "Expires");
4375 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
4376 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
4378 switch (msg->response) {
4379 case 200:
4380 if (expires == 0) {
4381 sip->registerstatus = 0;
4382 } else {
4383 gchar *contact_hdr = NULL;
4384 gchar *gruu = NULL;
4385 gchar *epid;
4386 gchar *uuid;
4387 gchar *timeout;
4388 gchar *server_hdr = sipmsg_find_header(msg, "Server");
4390 if (!sip->reregister_set) {
4391 gchar *action_name = g_strdup_printf("<%s>", "registration");
4392 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
4393 g_free(action_name);
4394 sip->reregister_set = TRUE;
4397 sip->registerstatus = 3;
4399 if (server_hdr && !sip->server_version) {
4400 sip->server_version = g_strdup(server_hdr);
4401 g_free(default_ua);
4402 default_ua = NULL;
4405 #ifdef USE_KERBEROS
4406 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4407 #endif
4408 tmp = sipmsg_find_auth_header(msg, "NTLM");
4409 #ifdef USE_KERBEROS
4410 } else {
4411 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4413 #endif
4414 if (tmp) {
4415 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
4416 fill_auth(tmp, &sip->registrar);
4419 if (!sip->reauthenticate_set) {
4420 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
4421 guint reauth_timeout;
4422 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
4423 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
4424 reauth_timeout = sip->registrar.expires - 300;
4425 } else {
4426 /* NTLM: we have to reauthenticate as our security token expires
4427 after eight hours (be five minutes early) */
4428 reauth_timeout = (8 * 3600) - 300;
4430 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
4431 g_free(action_name);
4432 sip->reauthenticate_set = TRUE;
4435 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
4437 epid = get_epid(sip);
4438 uuid = generateUUIDfromEPID(epid);
4439 g_free(epid);
4441 // There can be multiple Contact headers (one per location where the user is logged in) so
4442 // make sure to only get the one for this uuid
4443 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
4444 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
4445 if (valid_contact) {
4446 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
4447 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
4448 g_free(valid_contact);
4449 break;
4450 } else {
4451 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
4454 g_free(uuid);
4456 g_free(sip->contact);
4457 if(gruu) {
4458 sip->contact = g_strdup_printf("<%s>", gruu);
4459 g_free(gruu);
4460 } else {
4461 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
4462 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
4464 sip->ocs2007 = FALSE;
4465 sip->batched_support = FALSE;
4467 while(hdr)
4469 elem = hdr->data;
4470 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
4471 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
4472 /* We interpret this as OCS2007+ indicator */
4473 sip->ocs2007 = TRUE;
4474 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s (indicates OCS2007+)\n", elem->value);
4476 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
4477 sip->batched_support = TRUE;
4478 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s\n", elem->value);
4481 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
4482 gchar **caps = g_strsplit(elem->value,",",0);
4483 i = 0;
4484 while (caps[i]) {
4485 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
4486 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
4487 i++;
4489 g_strfreev(caps);
4491 hdr = g_slist_next(hdr);
4494 /* rejoin open chats to be able to use them by continue to send messages */
4495 purple_conversation_foreach(sipe_rejoin_chat);
4497 /* subscriptions */
4498 if (!sip->subscribed) { //do it just once, not every re-register
4500 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
4501 (GCompareFunc)g_ascii_strcasecmp)) {
4502 sipe_subscribe_roaming_contacts(sip);
4505 /* For 2007+ it does not make sence to subscribe to:
4506 * vnd-microsoft-roaming-ACL
4507 * vnd-microsoft-provisioning (not v2)
4508 * presence.wpending
4509 * These are for backward compatibility.
4511 if (sip->ocs2007)
4513 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
4514 (GCompareFunc)g_ascii_strcasecmp)) {
4515 sipe_subscribe_roaming_self(sip);
4517 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
4518 (GCompareFunc)g_ascii_strcasecmp)) {
4519 sipe_subscribe_roaming_provisioning_v2(sip);
4522 /* For 2005- servers */
4523 else
4525 //sipe_options_request(sip, sip->sipdomain);
4527 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
4528 (GCompareFunc)g_ascii_strcasecmp)) {
4529 sipe_subscribe_roaming_acl(sip);
4531 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
4532 (GCompareFunc)g_ascii_strcasecmp)) {
4533 sipe_subscribe_roaming_provisioning(sip);
4535 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
4536 (GCompareFunc)g_ascii_strcasecmp)) {
4537 sipe_subscribe_presence_wpending(sip, msg);
4540 /* For 2007+ we publish our initial statuses only after
4541 * received our existing publications in sipe_process_roaming_self()
4542 * Only in this case we know versions of current publications made
4543 * on our behalf.
4545 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
4548 sip->subscribed = TRUE;
4551 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
4552 "timeout=", ";", NULL);
4553 if (timeout != NULL) {
4554 sscanf(timeout, "%u", &sip->keepalive_timeout);
4555 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
4556 sip->keepalive_timeout);
4557 g_free(timeout);
4560 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip->cseq);
4562 break;
4563 case 301:
4565 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
4567 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
4568 gchar **parts = g_strsplit(redirect + 4, ";", 0);
4569 gchar **tmp;
4570 gchar *hostname;
4571 int port = 0;
4572 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
4573 int i = 1;
4575 tmp = g_strsplit(parts[0], ":", 0);
4576 hostname = g_strdup(tmp[0]);
4577 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
4578 g_strfreev(tmp);
4580 while (parts[i]) {
4581 tmp = g_strsplit(parts[i], "=", 0);
4582 if (tmp[1]) {
4583 if (g_strcasecmp("transport", tmp[0]) == 0) {
4584 if (g_strcasecmp("tcp", tmp[1]) == 0) {
4585 transport = SIPE_TRANSPORT_TCP;
4586 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
4587 transport = SIPE_TRANSPORT_UDP;
4591 g_strfreev(tmp);
4592 i++;
4594 g_strfreev(parts);
4596 /* Close old connection */
4597 sipe_connection_cleanup(sip);
4599 /* Create new connection */
4600 sip->transport = transport;
4601 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4602 hostname, port, TRANSPORT_DESCRIPTOR);
4603 create_connection(sip, hostname, port);
4605 g_free(redirect);
4607 break;
4608 case 401:
4609 if (sip->registerstatus != 2) {
4610 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
4611 if (sip->registrar.retries > 3) {
4612 sip->gc->wants_to_die = TRUE;
4613 purple_connection_error(sip->gc, _("Wrong password"));
4614 return TRUE;
4616 #ifdef USE_KERBEROS
4617 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4618 #endif
4619 tmp = sipmsg_find_auth_header(msg, "NTLM");
4620 #ifdef USE_KERBEROS
4621 } else {
4622 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4624 #endif
4625 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\n", tmp);
4626 fill_auth(tmp, &sip->registrar);
4627 sip->registerstatus = 2;
4628 if (sip->account->disconnecting) {
4629 do_register_exp(sip, 0);
4630 } else {
4631 do_register(sip);
4634 break;
4635 case 403:
4637 gchar *warning = sipmsg_find_header(msg, "Warning");
4638 gchar **reason = NULL;
4639 if (warning != NULL) {
4640 /* Example header:
4641 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4643 reason = g_strsplit(warning, "\"", 0);
4645 warning = g_strdup_printf(_("You have been rejected by the server: %s"),
4646 (reason && reason[1]) ? reason[1] : _("no reason given"));
4647 g_strfreev(reason);
4649 sip->gc->wants_to_die = TRUE;
4650 purple_connection_error(sip->gc, warning);
4651 g_free(warning);
4652 return TRUE;
4654 break;
4655 case 404:
4657 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4658 gchar *reason = NULL;
4659 if (warning != NULL) {
4660 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4662 warning = g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
4663 warning ? (reason ? reason : _("no reason given")) :
4664 _("SIP is either not enabled for the destination URI or it does not exist"));
4665 g_free(reason);
4667 sip->gc->wants_to_die = TRUE;
4668 purple_connection_error(sip->gc, warning);
4669 g_free(warning);
4670 return TRUE;
4672 break;
4673 case 503:
4674 case 504: /* Server time-out */
4676 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4677 gchar *reason = NULL;
4678 if (warning != NULL) {
4679 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4681 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4682 g_free(reason);
4684 sip->gc->wants_to_die = TRUE;
4685 purple_connection_error(sip->gc, warning);
4686 g_free(warning);
4687 return TRUE;
4689 break;
4691 return TRUE;
4695 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
4697 static const char*
4698 sipe_get_status_by_availability(int avail)
4700 const char *status;
4702 if (avail < 3000)
4703 status = SIPE_STATUS_ID_OFFLINE;
4704 else if (avail < 4500)
4705 status = SIPE_STATUS_ID_AVAILABLE;
4706 else if (avail < 6000)
4707 status = SIPE_STATUS_ID_IDLE;
4708 else if (avail < 7500)
4709 status = SIPE_STATUS_ID_BUSY;
4710 else if (avail < 9000)
4711 status = SIPE_STATUS_ID_AWAY;
4712 else if (avail < 12000)
4713 status = SIPE_STATUS_ID_DND;
4714 else if (avail < 15000)
4715 status = SIPE_STATUS_ID_BRB;
4716 else if (avail < 18000)
4717 status = SIPE_STATUS_ID_AWAY;
4718 else
4719 status = SIPE_STATUS_ID_OFFLINE;
4721 return status;
4724 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4726 const char *uri;
4727 xmlnode *xn_categories;
4728 xmlnode *xn_category;
4729 xmlnode *xn_node;
4731 xn_categories = xmlnode_from_str(data, len);
4732 uri = xmlnode_get_attrib(xn_categories, "uri"); /* with 'sip:' prefix */
4734 for (xn_category = xmlnode_get_child(xn_categories, "category");
4735 xn_category ;
4736 xn_category = xmlnode_get_next_twin(xn_category) )
4738 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4740 /* contactCard */
4741 if (!strcmp(attrVar, "contactCard"))
4743 xmlnode *node;
4744 /* identity - Display Name and email */
4745 node = xmlnode_get_descendant(xn_category, "contactCard", "identity", NULL);
4746 if (node) {
4747 char* display_name = xmlnode_get_data(
4748 xmlnode_get_descendant(node, "name", "displayName", NULL));
4749 char* email = xmlnode_get_data(
4750 xmlnode_get_child(node, "email"));
4752 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
4753 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
4755 g_free(display_name);
4756 g_free(email);
4758 /* company */
4759 node = xmlnode_get_descendant(xn_category, "contactCard", "company", NULL);
4760 if (node) {
4761 char* company = xmlnode_get_data(node);
4762 sipe_update_user_info(sip, uri, COMPANY_PROP, company);
4763 g_free(company);
4765 /* department */
4766 node = xmlnode_get_descendant(xn_category, "contactCard", "department", NULL);
4767 if (node) {
4768 char* department = xmlnode_get_data(node);
4769 sipe_update_user_info(sip, uri, DEPARTMENT_PROP, department);
4770 g_free(department);
4772 /* title */
4773 node = xmlnode_get_descendant(xn_category, "contactCard", "title", NULL);
4774 if (node) {
4775 char* title = xmlnode_get_data(node);
4776 sipe_update_user_info(sip, uri, TITLE_PROP, title);
4777 g_free(title);
4779 /* office */
4780 node = xmlnode_get_descendant(xn_category, "contactCard", "office", NULL);
4781 if (node) {
4782 char* office = xmlnode_get_data(node);
4783 sipe_update_user_info(sip, uri, OFFICE_PROP, office);
4784 g_free(office);
4786 /* site (url) */
4787 node = xmlnode_get_descendant(xn_category, "contactCard", "url", NULL);
4788 if (node) {
4789 char* site = xmlnode_get_data(node);
4790 sipe_update_user_info(sip, uri, SITE_PROP, site);
4791 g_free(site);
4793 /* phone */
4794 for (node = xmlnode_get_descendant(xn_category, "contactCard", "phone", NULL);
4795 node;
4796 node = xmlnode_get_next_twin(node))
4798 const char *phone_type = xmlnode_get_attrib(node, "type");
4799 char* phone = xmlnode_get_data(xmlnode_get_child(node, "uri"));
4800 char* phone_display_string = xmlnode_get_data(xmlnode_get_child(node, "displayString"));
4802 sipe_update_user_phone(sip, uri, phone_type, phone, phone_display_string);
4804 g_free(phone);
4805 g_free(phone_display_string);
4807 /* address */
4808 for (node = xmlnode_get_descendant(xn_category, "contactCard", "address", NULL);
4809 node;
4810 node = xmlnode_get_next_twin(node))
4812 if (!strcmp(xmlnode_get_attrib(node, "type"), "work")) {
4813 char* street = xmlnode_get_data(xmlnode_get_child(node, "street"));
4814 char* city = xmlnode_get_data(xmlnode_get_child(node, "city"));
4815 char* state = xmlnode_get_data(xmlnode_get_child(node, "state"));
4816 char* zipcode = xmlnode_get_data(xmlnode_get_child(node, "zipcode"));
4817 char* country_code = xmlnode_get_data(xmlnode_get_child(node, "countryCode"));
4819 sipe_update_user_info(sip, uri, ADDRESS_STREET_PROP, street);
4820 sipe_update_user_info(sip, uri, ADDRESS_CITY_PROP, city);
4821 sipe_update_user_info(sip, uri, ADDRESS_STATE_PROP, state);
4822 sipe_update_user_info(sip, uri, ADDRESS_ZIPCODE_PROP, zipcode);
4823 sipe_update_user_info(sip, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
4825 g_free(street);
4826 g_free(city);
4827 g_free(state);
4828 g_free(zipcode);
4829 g_free(country_code);
4831 break;
4835 /* note */
4836 else if (!strcmp(attrVar, "note"))
4838 if (uri) {
4839 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4841 if (sbuddy) {
4842 char *note;
4844 xn_node = xmlnode_get_child(xn_category, "note");
4845 if (!xn_node) continue;
4846 xn_node = xmlnode_get_child(xn_node, "body");
4847 if (!xn_node) continue;
4848 note = xmlnode_get_data(xn_node);
4849 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4850 g_free(sbuddy->annotation);
4851 sbuddy->annotation = NULL;
4852 if (note) sbuddy->annotation = g_strdup(note);
4853 g_free(note);
4858 /* state */
4859 else if(!strcmp(attrVar, "state"))
4861 char *data;
4862 int availability;
4863 const char *status;
4864 xmlnode *xn_availability;
4865 xmlnode *xn_activity;
4866 xmlnode *xn_meeting_subject;
4867 xmlnode *xn_meeting_location;
4868 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sip->buddies, uri) : NULL;
4870 xn_node = xmlnode_get_child(xn_category, "state");
4871 if (!xn_node) continue;
4872 xn_availability = xmlnode_get_child(xn_node, "availability");
4873 if (!xn_availability) continue;
4874 xn_activity = xmlnode_get_child(xn_node, "activity");
4875 xn_meeting_subject = xmlnode_get_child(xn_node, "meetingSubject");
4876 xn_meeting_location = xmlnode_get_child(xn_node, "meetingLocation");
4878 data = xmlnode_get_data(xn_availability);
4879 availability = atoi(data);
4880 g_free(data);
4882 /* activity, meeting_subject, meeting_location */
4883 if (sbuddy) {
4884 /* activity */
4885 g_free(sbuddy->activity);
4886 sbuddy->activity = NULL;
4887 if (xn_activity) {
4888 const char *token = xmlnode_get_attrib(xn_activity, "token");
4889 xmlnode *xn_custom = xmlnode_get_child(xn_activity, "custom");
4891 /* from token */
4892 if (!is_empty(token)) {
4893 if (!strcmp(token, "on-the-phone")) {
4894 sbuddy->activity = g_strdup(_("On The Phone"));
4895 } else if (!strcmp(token, "in-a-conference")) {
4896 sbuddy->activity = g_strdup(_("In a Conference"));
4897 } else if (!strcmp(token, "in-a-meeting")) {
4898 sbuddy->activity = g_strdup(_("In a Meeting"));
4899 } else if (!strcmp(token, "out-of-office")) {
4900 sbuddy->activity = g_strdup(_("Out of Office"));
4901 } else if (!strcmp(token, "urgent-interruptions-only")) {
4902 sbuddy->activity = g_strdup(_("Urgent Interruptions Only"));
4905 /* form custom element */
4906 if (xn_custom) {
4907 char *custom = xmlnode_get_data(xn_custom);
4909 if (!is_empty(custom)) {
4910 sbuddy->activity = custom;
4911 custom = NULL;
4913 g_free(custom);
4916 /* meeting_subject */
4917 g_free(sbuddy->meeting_subject);
4918 sbuddy->meeting_subject = NULL;
4919 if (xn_meeting_subject) {
4920 char *meeting_subject = xmlnode_get_data(xn_meeting_subject);
4922 if (!is_empty(meeting_subject)) {
4923 sbuddy->meeting_subject = meeting_subject;
4924 meeting_subject = NULL;
4926 g_free(meeting_subject);
4928 /* meeting_location */
4929 g_free(sbuddy->meeting_location);
4930 sbuddy->meeting_location = NULL;
4931 if (xn_meeting_location) {
4932 char *meeting_location = xmlnode_get_data(xn_meeting_location);
4934 if (!is_empty(meeting_location)) {
4935 sbuddy->meeting_location = meeting_location;
4936 meeting_location = NULL;
4938 g_free(meeting_location);
4942 status = sipe_get_status_by_availability(availability);
4943 if (status) {
4944 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status);
4945 purple_prpl_got_user_status(sip->account, uri, status, NULL);
4950 xmlnode_free(xn_categories);
4953 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4955 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4956 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4957 payload->host = g_strdup(host);
4958 payload->buddies = server;
4959 sipe_subscribe_presence_batched_routed(sip, payload);
4960 sipe_subscribe_presence_batched_routed_free(payload);
4963 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4965 xmlnode *xn_list;
4966 xmlnode *xn_resource;
4967 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4968 g_free, NULL);
4969 GSList *server;
4970 gchar *host;
4972 xn_list = xmlnode_from_str(data, len);
4974 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4975 xn_resource;
4976 xn_resource = xmlnode_get_next_twin(xn_resource) )
4978 const char *uri, *state;
4979 xmlnode *xn_instance;
4981 xn_instance = xmlnode_get_child(xn_resource, "instance");
4982 if (!xn_instance) continue;
4984 uri = xmlnode_get_attrib(xn_resource, "uri");
4985 state = xmlnode_get_attrib(xn_instance, "state");
4986 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4988 if (strstr(state, "resubscribe")) {
4989 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4991 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4992 gchar *user = g_strdup(uri);
4993 host = g_strdup(poolFqdn);
4994 server = g_hash_table_lookup(servers, host);
4995 server = g_slist_append(server, user);
4996 g_hash_table_insert(servers, host, server);
4997 } else {
4998 sipe_subscribe_presence_single(sip, (void *) uri);
5003 /* Send out any deferred poolFqdn subscriptions */
5004 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
5005 g_hash_table_destroy(servers);
5007 xmlnode_free(xn_list);
5010 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
5012 const gchar *uri;
5013 gchar *getbasic;
5014 gchar *activity = NULL;
5015 xmlnode *pidf;
5016 xmlnode *basicstatus = NULL, *tuple, *status;
5017 gboolean isonline = FALSE;
5018 xmlnode *display_name_node;
5020 pidf = xmlnode_from_str(data, len);
5021 if (!pidf) {
5022 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
5023 return;
5026 uri = xmlnode_get_attrib(pidf, "entity"); /* with 'sip:' prefix */
5028 if ((tuple = xmlnode_get_child(pidf, "tuple")))
5030 if ((status = xmlnode_get_child(tuple, "status"))) {
5031 basicstatus = xmlnode_get_child(status, "basic");
5035 if (!basicstatus) {
5036 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
5037 xmlnode_free(pidf);
5038 return;
5041 getbasic = xmlnode_get_data(basicstatus);
5042 if (!getbasic) {
5043 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
5044 xmlnode_free(pidf);
5045 return;
5048 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
5049 if (strstr(getbasic, "open")) {
5050 isonline = TRUE;
5052 g_free(getbasic);
5054 display_name_node = xmlnode_get_child(pidf, "display-name");
5055 if (display_name_node) {
5056 char * display_name = xmlnode_get_data(display_name_node);
5058 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5059 g_free(display_name);
5062 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
5063 if ((status = xmlnode_get_child(tuple, "status"))) {
5064 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
5065 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
5066 activity = xmlnode_get_data(basicstatus);
5067 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
5073 if (isonline) {
5074 const gchar * status_id = NULL;
5075 if (activity) {
5076 if (strstr(activity, "busy")) {
5077 status_id = SIPE_STATUS_ID_BUSY;
5078 } else if (strstr(activity, "away")) {
5079 status_id = SIPE_STATUS_ID_AWAY;
5083 if (!status_id) {
5084 status_id = SIPE_STATUS_ID_AVAILABLE;
5087 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
5088 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
5089 } else {
5090 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
5093 g_free(activity);
5094 xmlnode_free(pidf);
5097 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
5099 const char *activity = NULL;
5100 const char *epid;
5101 const char *status_id = NULL;
5102 const char *name;
5103 char *uri;
5104 int avl;
5105 int act;
5106 const char *device_name = NULL;
5107 struct sipe_buddy *sbuddy;
5108 xmlnode *node;
5109 xmlnode *xn_presentity;
5110 xmlnode *xn_availability;
5111 xmlnode *xn_activity;
5112 xmlnode *xn_display_name;
5113 xmlnode *xn_email;
5114 xmlnode *xn_phone_number;
5115 xmlnode *xn_userinfo;
5116 xmlnode *xn_oof;
5117 xmlnode *xn_contact;
5118 xmlnode *xn_note;
5119 char *note;
5120 char *free_activity;
5122 /* fix for Reuters environment on Linux */
5123 if (data && strstr(data, "encoding=\"utf-16\"")) {
5124 char *tmp_data;
5125 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
5126 xn_presentity = xmlnode_from_str(tmp_data, strlen(tmp_data));
5127 g_free(tmp_data);
5128 } else {
5129 xn_presentity = xmlnode_from_str(data, len);
5132 xn_availability = xmlnode_get_child(xn_presentity, "availability");
5133 xn_activity = xmlnode_get_child(xn_presentity, "activity");
5134 xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
5135 xn_email = xmlnode_get_child(xn_presentity, "email");
5136 xn_phone_number = xmlnode_get_child(xn_presentity, "phoneNumber");
5137 xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
5138 xn_oof = xn_userinfo ? xmlnode_get_child(xn_userinfo, "oof") : NULL;
5140 xn_contact = xn_userinfo ? xmlnode_get_child(xn_userinfo, "contact") : NULL;
5141 xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
5142 note = xn_note ? xmlnode_get_data(xn_note) : NULL;
5144 free_activity = NULL;
5146 name = xmlnode_get_attrib(xn_presentity, "uri"); /* without 'sip:' prefix */
5147 uri = sip_uri_from_name(name);
5148 avl = atoi(xmlnode_get_attrib(xn_availability, "aggregate"));
5149 epid = xmlnode_get_attrib(xn_availability, "epid");
5150 act = atoi(xmlnode_get_attrib(xn_activity, "aggregate"));
5152 if (xn_display_name) {
5153 char *display_name = g_strdup(xmlnode_get_attrib(xn_display_name, "displayName"));
5154 char *email = xn_email ? g_strdup(xmlnode_get_attrib(xn_email, "email")) : NULL;
5155 char *phone_label = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "label")) : NULL;
5156 char *phone_number = xn_phone_number ? g_strdup(xmlnode_get_attrib(xn_phone_number, "number")) : NULL;
5157 char *tel_uri = sip_to_tel_uri(phone_number);
5159 sipe_update_user_info(sip, uri, ALIAS_PROP, display_name);
5160 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
5161 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
5162 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
5164 g_free(tel_uri);
5165 g_free(phone_label);
5166 g_free(phone_number);
5167 g_free(email);
5168 g_free(display_name);
5171 if (xn_contact) {
5172 /* tel */
5173 for (node = xmlnode_get_child(xn_contact, "tel"); node; node = xmlnode_get_next_twin(node))
5175 /* Ex.: <tel type="work">tel:+3222220000</tel> */
5176 const char *phone_type = xmlnode_get_attrib(node, "type");
5177 char* phone = xmlnode_get_data(node);
5179 sipe_update_user_phone(sip, uri, phone_type, phone, NULL);
5181 g_free(phone);
5185 /* oof */
5186 if (xn_oof) {
5187 activity = _("Out of Office");
5190 /* devicePresence */
5191 for (node = xmlnode_get_descendant(xn_presentity, "devices", "devicePresence", NULL); node; node = xmlnode_get_next_twin(node)) {
5192 xmlnode *xn_device_name;
5193 xmlnode *xn_state;
5194 char *state;
5196 if (strcmp(xmlnode_get_attrib(node, "epid"), epid)) continue;
5198 xn_device_name = xmlnode_get_child(node, "deviceName");
5199 device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
5201 xn_state = xmlnode_get_descendant(node, "states", "state", NULL);
5202 state = xn_state ? xmlnode_get_data(xn_state) : NULL;
5204 if (!is_empty(state)) {
5205 if (!strcmp(state, "on-the-phone")) {
5206 activity = _("On The Phone");
5207 } else if (!strcmp(state, "presenting")) {
5208 activity = _("In a Conference");
5209 } else {
5210 activity = free_activity = state;
5211 state = NULL;
5214 g_free(state);
5218 /* [MS-SIP] 2.2.1 */
5219 if (act < 150)
5220 status_id = SIPE_STATUS_ID_AWAY;
5221 else if (act < 200)
5222 status_id = SIPE_STATUS_ID_LUNCH;
5223 else if (act < 300)
5224 status_id = SIPE_STATUS_ID_IDLE;
5225 else if (act < 400)
5226 status_id = SIPE_STATUS_ID_BRB;
5227 else if (act < 500)
5228 status_id = SIPE_STATUS_ID_AVAILABLE;
5229 else if (act < 600)
5230 status_id = SIPE_STATUS_ID_ONPHONE;
5231 else if (act < 700)
5232 status_id = SIPE_STATUS_ID_BUSY;
5233 else if (act < 800)
5234 status_id = SIPE_STATUS_ID_AWAY;
5235 else
5236 status_id = SIPE_STATUS_ID_AVAILABLE;
5238 if (avl < 100)
5239 status_id = SIPE_STATUS_ID_OFFLINE;
5242 sbuddy = g_hash_table_lookup(sip->buddies, uri);
5243 if (sbuddy)
5245 g_free(sbuddy->activity);
5246 sbuddy->activity = NULL;
5247 if (!is_empty(activity)) { sbuddy->activity = g_strdup(activity); }
5249 g_free(sbuddy->annotation);
5250 sbuddy->annotation = NULL;
5251 if (!is_empty(note)) { sbuddy->annotation = g_strdup(note); }
5253 g_free(sbuddy->device_name);
5254 sbuddy->device_name = NULL;
5255 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
5258 if (free_activity) g_free(free_activity);
5260 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id);
5261 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
5262 g_free(note);
5263 xmlnode_free(xn_presentity);
5264 g_free(uri);
5267 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
5269 char *ctype = sipmsg_find_header(msg, "Content-Type");
5271 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
5273 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
5274 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
5276 const char *content = msg->body;
5277 unsigned length = msg->bodylen;
5278 PurpleMimeDocument *mime = NULL;
5280 if (strstr(ctype, "multipart"))
5282 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
5283 const char *content_type;
5284 GList* parts;
5285 mime = purple_mime_document_parse(doc);
5286 parts = purple_mime_document_get_parts(mime);
5287 while(parts) {
5288 content = purple_mime_part_get_data(parts->data);
5289 length = purple_mime_part_get_length(parts->data);
5290 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
5291 if(content_type && strstr(content_type,"application/rlmi+xml"))
5293 process_incoming_notify_rlmi_resub(sip, content, length);
5295 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
5297 process_incoming_notify_msrtc(sip, content, length);
5299 else
5301 process_incoming_notify_rlmi(sip, content, length);
5303 parts = parts->next;
5305 g_free(doc);
5307 if (mime)
5309 purple_mime_document_free(mime);
5312 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
5314 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
5316 else if(strstr(ctype, "application/rlmi+xml"))
5318 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
5321 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
5323 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
5325 else
5327 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
5331 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
5333 char *ctype = sipmsg_find_header(msg, "Content-Type");
5334 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
5336 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
5338 if (ctype &&
5339 strstr(ctype, "multipart") &&
5340 (strstr(ctype, "application/rlmi+xml") ||
5341 strstr(ctype, "application/msrtc-event-categories+xml"))) {
5342 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
5343 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
5344 GList *parts = purple_mime_document_get_parts(mime);
5345 GSList *buddies = NULL;
5346 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
5348 while (parts) {
5349 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
5350 purple_mime_part_get_length(parts->data));
5352 if (strcmp(xml->name, "list")) {
5353 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
5355 buddies = g_slist_append(buddies, uri);
5357 xmlnode_free(xml);
5359 parts = parts->next;
5361 g_free(doc);
5362 if (mime) purple_mime_document_free(mime);
5364 payload->host = g_strdup(who);
5365 payload->buddies = buddies;
5366 sipe_schedule_action(action_name, timeout,
5367 sipe_subscribe_presence_batched_routed,
5368 sipe_subscribe_presence_batched_routed_free,
5369 sip, payload);
5370 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
5372 } else {
5373 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
5374 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
5376 g_free(action_name);
5380 * Dispatcher for all incoming subscription information
5381 * whether it comes from NOTIFY, BENOTIFY requests or
5382 * piggy-backed to subscription's OK responce.
5384 * @param request whether initiated from BE/NOTIFY request or OK-response message.
5385 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
5387 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
5389 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
5390 gchar *event = sipmsg_find_header(msg, "Event");
5391 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
5392 char *tmp;
5393 int timeout = 0;
5395 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
5396 event ? event : "",
5397 tmp = fix_newlines(msg->body));
5398 g_free(tmp);
5399 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state ? subscription_state : "");
5401 /* implicit subscriptions */
5402 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
5403 sipe_process_imdn(sip, msg);
5406 if (!request)
5408 const gchar *expires_header;
5409 expires_header = sipmsg_find_header(msg, "Expires");
5410 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
5411 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout);
5412 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout; // 2 min ahead of expiration
5415 /* for one off subscriptions (send with Expire: 0) */
5416 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
5418 sipe_process_provisioning_v2(sip, msg);
5420 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning"))
5422 sipe_process_provisioning(sip, msg);
5425 if (!subscription_state || strstr(subscription_state, "active"))
5427 if (event && !g_ascii_strcasecmp(event, "presence"))
5429 sipe_process_presence(sip, msg);
5431 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
5433 sipe_process_roaming_contacts(sip, msg);
5435 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
5437 sipe_process_roaming_self(sip, msg);
5439 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
5441 sipe_process_roaming_acl(sip, msg);
5443 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
5445 sipe_process_presence_wpending(sip, msg);
5447 else if (event && !g_ascii_strcasecmp(event, "conference"))
5449 sipe_process_conference(sip, msg);
5453 /* The server sends status 'terminated' */
5454 if (subscription_state && strstr(subscription_state, "terminated") ) {
5455 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
5456 gchar *key = sipe_get_subscription_key(event, who);
5458 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who);
5459 g_free(who);
5461 if (g_hash_table_lookup(sip->subscriptions, key)) {
5462 g_hash_table_remove(sip->subscriptions, key);
5463 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key);
5466 g_free(key);
5469 if (timeout && event) {// For LSC 2005 and OCS 2007
5470 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
5471 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
5473 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
5474 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
5475 g_free(action_name);
5477 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
5478 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
5480 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
5481 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
5482 g_free(action_name);
5484 else*/
5485 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
5486 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
5488 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
5489 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
5490 g_free(action_name);
5492 else if (!g_ascii_strcasecmp(event, "presence") &&
5493 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
5495 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
5496 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
5497 if (sip->batched_support) {
5498 sipe_process_presence_timeout(sip, msg, who, timeout);
5500 else {
5501 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(who));
5502 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who, timeout);
5504 g_free(action_name);
5505 g_free(who);
5509 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
5511 sipe_process_registration_notify(sip, msg);
5514 /* The client responses on received a NOTIFY message */
5515 if (request && !benotify)
5517 if (event) {
5518 gchar *who = parse_from(sipmsg_find_header(msg, "From"));
5519 gchar *key = sipe_get_subscription_key(event, who);
5521 g_free(who);
5522 if (!key || (key && g_hash_table_lookup(sip->subscriptions, key))) {
5523 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5524 } else {
5525 send_sip_response(sip->gc, msg, 481, "Call Leg Does Not Exist", NULL);
5528 g_free(key);
5529 } else {
5530 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5535 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
5537 int availability = 300; // online
5538 int activity = 400; // Available
5539 gchar *body;
5541 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
5542 activity = 100;
5543 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
5544 activity = 150;
5545 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
5546 activity = 300;
5547 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
5548 activity = 400;
5549 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
5550 activity = 500;
5551 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
5552 activity = 600;
5553 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
5554 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
5555 availability = 0; // offline
5556 activity = 100;
5557 } else {
5558 activity = 400; // available
5561 //@TODO: send user data - state; add hostname in upper case
5562 body = g_markup_printf_escaped(note ? SIPE_SOAP_SET_PRESENCE(SIPE_SOAP_SET_PRESENCE_NOTE_XML) :
5563 SIPE_SOAP_SET_PRESENCE(SIPE_SOAP_SET_PRESENCE_NOTE_XML_EMPTY),
5564 sip->username,
5565 availability,
5566 activity,
5567 note ? note : "",
5568 note ? note : "");
5569 send_soap_request(sip, body);
5570 g_free(body);
5573 static gboolean
5574 process_send_presence_category_publish_response(struct sipe_account_data *sip,
5575 struct sipmsg *msg,
5576 struct transaction *trans)
5578 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
5580 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
5581 xmlnode *xml;
5582 xmlnode *node;
5583 gchar *fault_code;
5584 GHashTable *faults;
5585 int index_our;
5586 gboolean has_device_publication = FALSE;
5588 xml = xmlnode_from_str(msg->body, msg->bodylen);
5590 /* test if version mismatch fault */
5591 fault_code = xmlnode_get_data(xmlnode_get_child(xml, "Faultcode"));
5592 if (strcmp(fault_code, "Client.BadCall.WrongDelta")) {
5593 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code);
5594 g_free(fault_code);
5595 xmlnode_free(xml);
5596 return TRUE;
5598 g_free(fault_code);
5600 /* accumulating information about faulty versions */
5601 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
5602 for (node = xmlnode_get_descendant(xml, "details", "operation", NULL);
5603 node;
5604 node = xmlnode_get_next_twin(node))
5606 const gchar *index = xmlnode_get_attrib(node, "index");
5607 const gchar *curVersion = xmlnode_get_attrib(node, "curVersion");
5609 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
5610 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index, curVersion);
5612 xmlnode_free(xml);
5614 /* here we are parsing own request to figure out what publication
5615 * referensed here only by index went wrong
5617 xml = xmlnode_from_str(trans->msg->body, trans->msg->bodylen);
5619 /* publication */
5620 for (node = xmlnode_get_descendant(xml, "publications", "publication", NULL),
5621 index_our = 1; /* starts with 1 - our first publication */
5622 node;
5623 node = xmlnode_get_next_twin(node), index_our++)
5625 gchar *idx = g_strdup_printf("%d", index_our);
5626 const gchar *curVersion = g_hash_table_lookup(faults, idx);
5627 const gchar *categoryName = xmlnode_get_attrib(node, "categoryName");
5628 g_free(idx);
5630 if (!strcmp("device", categoryName)) {
5631 has_device_publication = TRUE;
5634 if (curVersion) { /* fault exist on this index */
5635 const gchar *container = xmlnode_get_attrib(node, "container");
5636 const gchar *instance = xmlnode_get_attrib(node, "instance");
5637 /* key is <category><instance><container> */
5638 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5639 struct sipe_publication *publication =
5640 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, categoryName), key);
5642 purple_debug_info("sipe", "key is %s\n", key);
5644 if (publication) {
5645 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
5646 key, curVersion, publication->version);
5647 /* updating publication's version to the correct one */
5648 publication->version = atoi(curVersion);
5650 g_free(key);
5653 xmlnode_free(xml);
5654 g_hash_table_destroy(faults);
5656 /* rebublishing with right versions */
5657 if (has_device_publication) {
5658 send_publish_category_initial(sip);
5659 } else {
5660 send_presence_status(sip);
5663 return TRUE;
5667 * Returns 'device' XML part for publication.
5668 * Must be g_free'd after use.
5670 static gchar *
5671 sipe_publish_get_category_device(struct sipe_account_data *sip)
5673 gchar *uri;
5674 gchar *doc;
5675 gchar *epid = get_epid(sip);
5676 gchar *uuid = generateUUIDfromEPID(epid);
5677 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
5678 /* key is <category><instance><container> */
5679 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5680 struct sipe_publication *publication =
5681 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5683 g_free(key);
5684 g_free(epid);
5686 uri = sip_uri_self(sip);
5687 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5688 device_instance,
5689 publication ? publication->version : 0,
5690 uuid,
5691 uri,
5692 "00:00:00+01:00", /* @TODO make timezone real*/
5693 sipe_get_host_name()
5696 g_free(uri);
5697 g_free(uuid);
5699 return doc;
5703 * A service method - use
5704 * - send_publish_get_category_state_machine and
5705 * - send_publish_get_category_state_user instead.
5706 * Must be g_free'd after use.
5708 static gchar *
5709 sipe_publish_get_category_state(struct sipe_account_data *sip,
5710 gboolean is_user_state)
5712 int availability;
5713 guint instance = is_user_state ? sipe_get_pub_instance(sip, SIPE_PUB_STATE_USER) :
5714 sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
5715 /* key is <category><instance><container> */
5716 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5717 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5718 struct sipe_publication *publication_2 =
5719 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5720 struct sipe_publication *publication_3 =
5721 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5723 g_free(key_2);
5724 g_free(key_3);
5726 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
5727 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
5728 availability = 15500;
5729 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
5730 availability = 12500;
5731 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
5732 availability = 9500;
5733 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY) ||
5734 !strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
5735 availability = 6500;
5736 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
5737 availability = 3500;
5738 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
5739 availability = 0;
5740 } else {
5741 // Offline or invisible
5742 availability = 18500;
5745 if (publication_2 && (publication_2->availability == availability))
5747 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
5748 return NULL; /* nothing to update */
5751 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5752 instance,
5753 publication_2 ? publication_2->version : 0,
5754 availability,
5755 instance,
5756 publication_3 ? publication_3->version : 0,
5757 availability);
5761 * Returns 'machineState' XML part for publication.
5762 * Must be g_free'd after use.
5764 static gchar *
5765 sipe_publish_get_category_state_machine(struct sipe_account_data *sip)
5767 return sipe_publish_get_category_state(sip, FALSE);
5771 * Returns 'userState' XML part for publication.
5772 * Must be g_free'd after use.
5774 static gchar *
5775 sipe_publish_get_category_state_user(struct sipe_account_data *sip)
5777 return sipe_publish_get_category_state(sip, TRUE);
5781 * Returns 'note' XML part for publication.
5782 * Must be g_free'd after use.
5784 static gchar *
5785 sipe_publish_get_category_note(struct sipe_account_data *sip, const char *note)
5787 /* key is <category><instance><container> */
5788 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", 0, 200);
5789 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", 0, 300);
5790 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", 0, 400);
5791 struct sipe_publication *publication_note_200 =
5792 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5793 struct sipe_publication *publication_note_300 =
5794 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5795 struct sipe_publication *publication_note_400 =
5796 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5798 const char *n1 = note;
5799 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5801 g_free(key_note_200);
5802 g_free(key_note_300);
5803 g_free(key_note_400);
5805 if (((!n1 || !strlen(n1)) && (!n2 || !strlen(n2))) /* both empty */
5806 || (n1 && n2 && !strcmp(n1, n2))) /* or not empty and equal */
5808 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
5809 return NULL; /* nothing to update */
5812 return g_markup_printf_escaped(SIPE_PUB_XML_NOTE,
5813 publication_note_200 ? publication_note_200->version : 0,
5814 note ? note : "",
5815 publication_note_300 ? publication_note_300->version : 0,
5816 note ? note : "",
5817 publication_note_400 ? publication_note_400->version : 0,
5818 note ? note : "");
5821 static void send_presence_publish(struct sipe_account_data *sip, const char *publications)
5823 gchar *uri;
5824 gchar *doc;
5825 gchar *tmp;
5826 gchar *hdr;
5828 uri = sip_uri_self(sip);
5829 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5830 uri,
5831 publications);
5833 tmp = get_contact(sip);
5834 hdr = g_strdup_printf("Contact: %s\r\n"
5835 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5837 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
5839 g_free(tmp);
5840 g_free(hdr);
5841 g_free(uri);
5842 g_free(doc);
5845 static void
5846 send_publish_category_initial(struct sipe_account_data *sip)
5848 gchar *pub_device = sipe_publish_get_category_device(sip);
5849 gchar *pub_machine = sipe_publish_get_category_state_machine(sip);
5850 gchar *publications = g_strdup_printf("%s%s",
5851 pub_device,
5852 pub_machine ? pub_machine : "");
5853 g_free(pub_device);
5854 g_free(pub_machine);
5856 send_presence_publish(sip, publications);
5857 g_free(publications);
5860 static void
5861 send_presence_category_publish(struct sipe_account_data *sip,
5862 const char *note)
5865 * Whether user manually changed status or
5866 * it was changed automatically due to user
5867 * became inactive/active again
5869 gboolean is_machine = (sip->was_idle && !sip->is_idle) || (!sip->was_idle && sip->is_idle);
5870 gchar *pub_state = is_machine ? sipe_publish_get_category_state_machine(sip) :
5871 sipe_publish_get_category_state_user(sip);
5872 gchar *pub_note = sipe_publish_get_category_note(sip, note);
5873 gchar *publications;
5875 if (!pub_state && !pub_note) {
5876 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
5877 return;
5880 publications = g_strdup_printf("%s%s",
5881 pub_state ? pub_state : "",
5882 pub_note ? pub_note : "");
5884 purple_debug_info("sipe", "send_presence_category_publish: sip->status: %s sip->is_idle:%s sip->was_idle:%s\n",
5885 sip->status, sip->is_idle ? "Y" : "N", sip->was_idle ? "Y" : "N");
5887 g_free(pub_state);
5888 g_free(pub_note);
5890 send_presence_publish(sip, publications);
5891 g_free(publications);
5894 static void send_presence_status(struct sipe_account_data *sip)
5896 PurpleStatus * status = purple_account_get_active_status(sip->account);
5897 const gchar *note;
5898 if (!status) return;
5900 note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
5901 purple_debug_info("sipe", "send_presence_status: note: '%s'\n", note ? note : "");
5903 if(sip->ocs2007){
5904 send_presence_category_publish(sip, note);
5905 } else {
5906 send_presence_soap(sip, note);
5910 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
5912 gboolean found = FALSE;
5913 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
5914 if (msg->response == 0) { /* request */
5915 if (!strcmp(msg->method, "MESSAGE")) {
5916 process_incoming_message(sip, msg);
5917 found = TRUE;
5918 } else if (!strcmp(msg->method, "NOTIFY")) {
5919 purple_debug_info("sipe","send->process_incoming_notify\n");
5920 process_incoming_notify(sip, msg, TRUE, FALSE);
5921 found = TRUE;
5922 } else if (!strcmp(msg->method, "BENOTIFY")) {
5923 purple_debug_info("sipe","send->process_incoming_benotify\n");
5924 process_incoming_notify(sip, msg, TRUE, TRUE);
5925 found = TRUE;
5926 } else if (!strcmp(msg->method, "INVITE")) {
5927 process_incoming_invite(sip, msg);
5928 found = TRUE;
5929 } else if (!strcmp(msg->method, "REFER")) {
5930 process_incoming_refer(sip, msg);
5931 found = TRUE;
5932 } else if (!strcmp(msg->method, "OPTIONS")) {
5933 process_incoming_options(sip, msg);
5934 found = TRUE;
5935 } else if (!strcmp(msg->method, "INFO")) {
5936 process_incoming_info(sip, msg);
5937 found = TRUE;
5938 } else if (!strcmp(msg->method, "ACK")) {
5939 // ACK's don't need any response
5940 found = TRUE;
5941 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
5942 // LCS 2005 sends us these - just respond 200 OK
5943 found = TRUE;
5944 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5945 } else if (!strcmp(msg->method, "BYE")) {
5946 process_incoming_bye(sip, msg);
5947 found = TRUE;
5948 } else {
5949 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5951 } else { /* response */
5952 struct transaction *trans = transactions_find(sip, msg);
5953 if (trans) {
5954 if (msg->response == 407) {
5955 gchar *resend, *auth, *ptmp;
5957 if (sip->proxy.retries > 30) return;
5958 sip->proxy.retries++;
5959 /* do proxy authentication */
5961 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
5963 fill_auth(ptmp, &sip->proxy);
5964 auth = auth_header(sip, &sip->proxy, trans->msg);
5965 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
5966 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
5967 g_free(auth);
5968 resend = sipmsg_to_string(trans->msg);
5969 /* resend request */
5970 sendout_pkt(sip->gc, resend);
5971 g_free(resend);
5972 } else {
5973 if (msg->response < 200) {
5974 /* ignore provisional response */
5975 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg->response);
5976 } else {
5977 sip->proxy.retries = 0;
5978 if (!strcmp(trans->msg->method, "REGISTER")) {
5979 if (msg->response == 401)
5981 sip->registrar.retries++;
5983 else
5985 sip->registrar.retries = 0;
5987 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip->cseq);
5988 } else {
5989 if (msg->response == 401) {
5990 gchar *resend, *auth, *ptmp;
5992 if (sip->registrar.retries > 4) return;
5993 sip->registrar.retries++;
5995 #ifdef USE_KERBEROS
5996 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5997 #endif
5998 ptmp = sipmsg_find_auth_header(msg, "NTLM");
5999 #ifdef USE_KERBEROS
6000 } else {
6001 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
6003 #endif
6005 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\n", ptmp);
6007 fill_auth(ptmp, &sip->registrar);
6008 auth = auth_header(sip, &sip->registrar, trans->msg);
6009 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
6010 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
6012 //sipmsg_remove_header_now(trans->msg, "Authorization");
6013 //sipmsg_add_header(trans->msg, "Authorization", auth);
6014 g_free(auth);
6015 resend = sipmsg_to_string(trans->msg);
6016 /* resend request */
6017 sendout_pkt(sip->gc, resend);
6018 g_free(resend);
6022 if (trans->callback) {
6023 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\n");
6024 /* call the callback to process response*/
6025 (trans->callback)(sip, msg, trans);
6028 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\n", sip->cseq);
6029 transactions_remove(sip, trans);
6033 found = TRUE;
6034 } else {
6035 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
6038 if (!found) {
6039 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
6043 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
6045 char *cur;
6046 char *dummy;
6047 char *tmp;
6048 struct sipmsg *msg;
6049 int restlen;
6050 cur = conn->inbuf;
6052 /* according to the RFC remove CRLF at the beginning */
6053 while (*cur == '\r' || *cur == '\n') {
6054 cur++;
6056 if (cur != conn->inbuf) {
6057 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
6058 conn->inbufused = strlen(conn->inbuf);
6061 /* Received a full Header? */
6062 sip->processing_input = TRUE;
6063 while (sip->processing_input &&
6064 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
6065 time_t currtime = time(NULL);
6066 cur += 2;
6067 cur[0] = '\0';
6068 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
6069 g_free(tmp);
6070 msg = sipmsg_parse_header(conn->inbuf);
6071 cur[0] = '\r';
6072 cur += 2;
6073 restlen = conn->inbufused - (cur - conn->inbuf);
6074 if (msg && restlen >= msg->bodylen) {
6075 dummy = g_malloc(msg->bodylen + 1);
6076 memcpy(dummy, cur, msg->bodylen);
6077 dummy[msg->bodylen] = '\0';
6078 msg->body = dummy;
6079 cur += msg->bodylen;
6080 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
6081 conn->inbufused = strlen(conn->inbuf);
6082 } else {
6083 if (msg){
6084 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
6085 sipmsg_free(msg);
6087 return;
6090 /*if (msg->body) {
6091 purple_debug_info("sipe", "body:\n%s", msg->body);
6094 // Verify the signature before processing it
6095 if (sip->registrar.gssapi_context) {
6096 struct sipmsg_breakdown msgbd;
6097 gchar *signature_input_str;
6098 gchar *rspauth;
6099 msgbd.msg = msg;
6100 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
6101 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
6103 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
6105 if (rspauth != NULL) {
6106 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
6107 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
6108 process_input_message(sip, msg);
6109 } else {
6110 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
6111 purple_connection_error(sip->gc, _("Invalid message signature received"));
6112 sip->gc->wants_to_die = TRUE;
6114 } else if (msg->response == 401) {
6115 purple_connection_error(sip->gc, _("Wrong password"));
6116 sip->gc->wants_to_die = TRUE;
6118 g_free(signature_input_str);
6120 g_free(rspauth);
6121 sipmsg_breakdown_free(&msgbd);
6122 } else {
6123 process_input_message(sip, msg);
6126 sipmsg_free(msg);
6130 static void sipe_udp_process(gpointer data, gint source,
6131 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
6133 PurpleConnection *gc = data;
6134 struct sipe_account_data *sip = gc->proto_data;
6135 struct sipmsg *msg;
6136 int len;
6137 time_t currtime;
6139 static char buffer[65536];
6140 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
6141 buffer[len] = '\0';
6142 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime), buffer);
6143 msg = sipmsg_parse_msg(buffer);
6144 if (msg) process_input_message(sip, msg);
6148 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
6150 struct sipe_account_data *sip = gc->proto_data;
6151 PurpleSslConnection *gsc = sip->gsc;
6153 purple_debug_error("sipe", "%s",debug);
6154 purple_connection_error(gc, msg);
6156 /* Invalidate this connection. Next send will open a new one */
6157 if (gsc) {
6158 connection_remove(sip, gsc->fd);
6159 purple_ssl_close(gsc);
6161 sip->gsc = NULL;
6162 sip->fd = -1;
6165 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
6166 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
6168 PurpleConnection *gc = data;
6169 struct sipe_account_data *sip;
6170 struct sip_connection *conn;
6171 int readlen, len;
6172 gboolean firstread = TRUE;
6174 /* NOTE: This check *IS* necessary */
6175 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
6176 purple_ssl_close(gsc);
6177 return;
6180 sip = gc->proto_data;
6181 conn = connection_find(sip, gsc->fd);
6182 if (conn == NULL) {
6183 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
6184 gc->wants_to_die = TRUE;
6185 purple_connection_error(gc, _("Connection not found. Please try to connect again"));
6186 return;
6189 /* Read all available data from the SSL connection */
6190 do {
6191 /* Increase input buffer size as needed */
6192 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
6193 conn->inbuflen += SIMPLE_BUF_INC;
6194 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
6195 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
6198 /* Try to read as much as there is space left in the buffer */
6199 readlen = conn->inbuflen - conn->inbufused - 1;
6200 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
6202 if (len < 0 && errno == EAGAIN) {
6203 /* Try again later */
6204 return;
6205 } else if (len < 0) {
6206 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
6207 return;
6208 } else if (firstread && (len == 0)) {
6209 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
6210 return;
6213 conn->inbufused += len;
6214 firstread = FALSE;
6216 /* Equivalence indicates that there is possibly more data to read */
6217 } while (len == readlen);
6219 conn->inbuf[conn->inbufused] = '\0';
6220 process_input(sip, conn);
6224 static void sipe_input_cb(gpointer data, gint source,
6225 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
6227 PurpleConnection *gc = data;
6228 struct sipe_account_data *sip = gc->proto_data;
6229 int len;
6230 struct sip_connection *conn = connection_find(sip, source);
6231 if (!conn) {
6232 purple_debug_error("sipe", "Connection not found!\n");
6233 return;
6236 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
6237 conn->inbuflen += SIMPLE_BUF_INC;
6238 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
6241 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
6243 if (len < 0 && errno == EAGAIN)
6244 return;
6245 else if (len <= 0) {
6246 purple_debug_info("sipe", "sipe_input_cb: read error\n");
6247 connection_remove(sip, source);
6248 if (sip->fd == source) sip->fd = -1;
6249 return;
6252 conn->inbufused += len;
6253 conn->inbuf[conn->inbufused] = '\0';
6255 process_input(sip, conn);
6258 /* Callback for new connections on incoming TCP port */
6259 static void sipe_newconn_cb(gpointer data, gint source,
6260 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
6262 PurpleConnection *gc = data;
6263 struct sipe_account_data *sip = gc->proto_data;
6264 struct sip_connection *conn;
6266 int newfd = accept(source, NULL, NULL);
6268 conn = connection_create(sip, newfd);
6270 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
6273 static void login_cb(gpointer data, gint source,
6274 SIPE_UNUSED_PARAMETER const gchar *error_message)
6276 PurpleConnection *gc = data;
6277 struct sipe_account_data *sip;
6278 struct sip_connection *conn;
6280 if (!PURPLE_CONNECTION_IS_VALID(gc))
6282 if (source >= 0)
6283 close(source);
6284 return;
6287 if (source < 0) {
6288 purple_connection_error(gc, _("Could not connect"));
6289 return;
6292 sip = gc->proto_data;
6293 sip->fd = source;
6294 sip->last_keepalive = time(NULL);
6296 conn = connection_create(sip, source);
6298 do_register(sip);
6300 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
6303 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
6304 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
6306 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
6307 if (sip == NULL) return;
6309 do_register(sip);
6312 static guint sipe_ht_hash_nick(const char *nick)
6314 char *lc = g_utf8_strdown(nick, -1);
6315 guint bucket = g_str_hash(lc);
6316 g_free(lc);
6318 return bucket;
6321 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
6323 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
6326 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
6328 struct sipe_account_data *sip = (struct sipe_account_data*) data;
6330 sip->listen_data = NULL;
6332 if (listenfd == -1) {
6333 purple_connection_error(sip->gc, _("Could not create listen socket"));
6334 return;
6337 sip->fd = listenfd;
6339 sip->listenport = purple_network_get_port_from_fd(sip->fd);
6340 sip->listenfd = sip->fd;
6342 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
6344 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
6345 do_register(sip);
6348 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
6349 SIPE_UNUSED_PARAMETER const char *error_message)
6351 struct sipe_account_data *sip = (struct sipe_account_data*) data;
6353 sip->query_data = NULL;
6355 if (!hosts || !hosts->data) {
6356 purple_connection_error(sip->gc, _("Could not resolve hostname"));
6357 return;
6360 hosts = g_slist_remove(hosts, hosts->data);
6361 g_free(sip->serveraddr);
6362 sip->serveraddr = hosts->data;
6363 hosts = g_slist_remove(hosts, hosts->data);
6364 while (hosts) {
6365 hosts = g_slist_remove(hosts, hosts->data);
6366 g_free(hosts->data);
6367 hosts = g_slist_remove(hosts, hosts->data);
6370 /* create socket for incoming connections */
6371 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
6372 sipe_udp_host_resolved_listen_cb, sip);
6373 if (sip->listen_data == NULL) {
6374 purple_connection_error(sip->gc, _("Could not create listen socket"));
6375 return;
6379 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
6380 PurpleSslErrorType error,
6381 gpointer data)
6383 PurpleConnection *gc = data;
6384 struct sipe_account_data *sip;
6386 /* If the connection is already disconnected, we don't need to do anything else */
6387 if (!PURPLE_CONNECTION_IS_VALID(gc))
6388 return;
6390 sip = gc->proto_data;
6391 sip->fd = -1;
6392 sip->gsc = NULL;
6394 switch(error) {
6395 case PURPLE_SSL_CONNECT_FAILED:
6396 purple_connection_error(gc, _("Connection failed"));
6397 break;
6398 case PURPLE_SSL_HANDSHAKE_FAILED:
6399 purple_connection_error(gc, _("SSL handshake failed"));
6400 break;
6401 case PURPLE_SSL_CERTIFICATE_INVALID:
6402 purple_connection_error(gc, _("SSL certificate invalid"));
6403 break;
6407 static void
6408 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
6410 struct sipe_account_data *sip = (struct sipe_account_data*) data;
6411 PurpleProxyConnectData *connect_data;
6413 sip->listen_data = NULL;
6415 sip->listenfd = listenfd;
6416 if (sip->listenfd == -1) {
6417 purple_connection_error(sip->gc, _("Could not create listen socket"));
6418 return;
6421 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
6422 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
6423 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
6424 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
6425 sipe_newconn_cb, sip->gc);
6426 purple_debug_info("sipe", "connecting to %s port %d\n",
6427 sip->realhostname, sip->realport);
6428 /* open tcp connection to the server */
6429 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
6430 sip->realport, login_cb, sip->gc);
6432 if (connect_data == NULL) {
6433 purple_connection_error(sip->gc, _("Could not create socket"));
6437 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
6439 PurpleAccount *account = sip->account;
6440 PurpleConnection *gc = sip->gc;
6442 if (port == 0) {
6443 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
6446 sip->realhostname = hostname;
6447 sip->realport = port;
6449 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
6450 hostname, port);
6452 /* TODO: is there a good default grow size? */
6453 if (sip->transport != SIPE_TRANSPORT_UDP)
6454 sip->txbuf = purple_circ_buffer_new(0);
6456 if (sip->transport == SIPE_TRANSPORT_TLS) {
6457 /* SSL case */
6458 if (!purple_ssl_is_supported()) {
6459 gc->wants_to_die = TRUE;
6460 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
6461 return;
6464 purple_debug_info("sipe", "using SSL\n");
6466 sip->gsc = purple_ssl_connect(account, hostname, port,
6467 login_cb_ssl, sipe_ssl_connect_failure, gc);
6468 if (sip->gsc == NULL) {
6469 purple_connection_error(gc, _("Could not create SSL context"));
6470 return;
6472 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
6473 /* UDP case */
6474 purple_debug_info("sipe", "using UDP\n");
6476 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
6477 if (sip->query_data == NULL) {
6478 purple_connection_error(gc, _("Could not resolve hostname"));
6480 } else {
6481 /* TCP case */
6482 purple_debug_info("sipe", "using TCP\n");
6483 /* create socket for incoming connections */
6484 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
6485 sipe_tcp_connect_listen_cb, sip);
6486 if (sip->listen_data == NULL) {
6487 purple_connection_error(gc, _("Could not create listen socket"));
6488 return;
6493 /* Service list for autodection */
6494 static const struct sipe_service_data service_autodetect[] = {
6495 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
6496 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
6497 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
6498 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
6499 { NULL, NULL, 0 }
6502 /* Service list for SSL/TLS */
6503 static const struct sipe_service_data service_tls[] = {
6504 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
6505 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
6506 { NULL, NULL, 0 }
6509 /* Service list for TCP */
6510 static const struct sipe_service_data service_tcp[] = {
6511 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
6512 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
6513 { NULL, NULL, 0 }
6516 /* Service list for UDP */
6517 static const struct sipe_service_data service_udp[] = {
6518 { "sip", "udp", SIPE_TRANSPORT_UDP },
6519 { NULL, NULL, 0 }
6522 static void srvresolved(PurpleSrvResponse *, int, gpointer);
6523 static void resolve_next_service(struct sipe_account_data *sip,
6524 const struct sipe_service_data *start)
6526 if (start) {
6527 sip->service_data = start;
6528 } else {
6529 sip->service_data++;
6530 if (sip->service_data->service == NULL) {
6531 gchar *hostname;
6532 /* Try connecting to the SIP hostname directly */
6533 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
6534 if (sip->auto_transport) {
6535 // If SSL is supported, default to using it; OCS servers aren't configured
6536 // by default to accept TCP
6537 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
6538 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
6539 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
6542 hostname = g_strdup(sip->sipdomain);
6543 create_connection(sip, hostname, 0);
6544 return;
6548 /* Try to resolve next service */
6549 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
6550 sip->service_data->transport,
6551 sip->sipdomain,
6552 srvresolved, sip);
6555 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
6557 struct sipe_account_data *sip = data;
6559 sip->srv_query_data = NULL;
6561 /* find the host to connect to */
6562 if (results) {
6563 gchar *hostname = g_strdup(resp->hostname);
6564 int port = resp->port;
6565 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
6566 hostname, port);
6567 g_free(resp);
6569 sip->transport = sip->service_data->type;
6571 create_connection(sip, hostname, port);
6572 } else {
6573 resolve_next_service(sip, NULL);
6577 static void sipe_login(PurpleAccount *account)
6579 PurpleConnection *gc;
6580 struct sipe_account_data *sip;
6581 gchar **signinname_login, **userserver;
6582 const char *transport;
6584 const char *username = purple_account_get_username(account);
6585 gc = purple_account_get_connection(account);
6587 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
6589 if (strpbrk(username, "\t\v\r\n") != NULL) {
6590 gc->wants_to_die = TRUE;
6591 purple_connection_error(gc, _("SIP Exchange user name contains invalid characters"));
6592 return;
6595 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
6596 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
6597 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
6598 sip->gc = gc;
6599 sip->account = account;
6600 sip->reregister_set = FALSE;
6601 sip->reauthenticate_set = FALSE;
6602 sip->subscribed = FALSE;
6603 sip->subscribed_buddies = FALSE;
6604 sip->initial_state_published = FALSE;
6606 /* username format: <username>,[<optional login>] */
6607 signinname_login = g_strsplit(username, ",", 2);
6608 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
6610 /* ensure that username format is name@domain */
6611 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
6612 g_strfreev(signinname_login);
6613 gc->wants_to_die = TRUE;
6614 purple_connection_error(gc, _("User name should be a valid SIP URI\nExample: user@company.com"));
6615 return;
6617 sip->username = g_strdup(signinname_login[0]);
6619 /* login name specified? */
6620 if (signinname_login[1] && strlen(signinname_login[1])) {
6621 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
6622 gboolean has_domain = domain_user[1] != NULL;
6623 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
6624 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
6625 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
6626 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
6627 sip->authdomain ? sip->authdomain : "", sip->authuser);
6628 g_strfreev(domain_user);
6631 userserver = g_strsplit(signinname_login[0], "@", 2);
6632 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
6633 purple_connection_set_display_name(gc, userserver[0]);
6634 sip->sipdomain = g_strdup(userserver[1]);
6635 g_strfreev(userserver);
6636 g_strfreev(signinname_login);
6638 if (strchr(sip->username, ' ') != NULL) {
6639 gc->wants_to_die = TRUE;
6640 purple_connection_error(gc, _("SIP Exchange user name contains whitespace"));
6641 return;
6644 sip->password = g_strdup(purple_connection_get_password(gc));
6646 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
6647 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
6648 g_free, (GDestroyNotify)g_hash_table_destroy);
6649 sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal,
6650 g_free, (GDestroyNotify)sipe_subscription_free);
6652 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
6654 sip->status = g_strdup(purple_status_get_id(purple_account_get_active_status(account)));
6656 sip->auto_transport = FALSE;
6657 transport = purple_account_get_string(account, "transport", "auto");
6658 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
6659 if (userserver[0]) {
6660 /* Use user specified server[:port] */
6661 int port = 0;
6663 if (userserver[1])
6664 port = atoi(userserver[1]);
6666 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
6667 userserver[0], port);
6669 if (strcmp(transport, "auto") == 0) {
6670 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
6671 } else if (strcmp(transport, "tls") == 0) {
6672 sip->transport = SIPE_TRANSPORT_TLS;
6673 } else if (strcmp(transport, "tcp") == 0) {
6674 sip->transport = SIPE_TRANSPORT_TCP;
6675 } else {
6676 sip->transport = SIPE_TRANSPORT_UDP;
6679 create_connection(sip, g_strdup(userserver[0]), port);
6680 } else {
6681 /* Server auto-discovery */
6682 if (strcmp(transport, "auto") == 0) {
6683 sip->auto_transport = TRUE;
6684 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
6685 } else if (strcmp(transport, "tls") == 0) {
6686 resolve_next_service(sip, service_tls);
6687 } else if (strcmp(transport, "tcp") == 0) {
6688 resolve_next_service(sip, service_tcp);
6689 } else {
6690 resolve_next_service(sip, service_udp);
6693 g_strfreev(userserver);
6696 static void sipe_connection_cleanup(struct sipe_account_data *sip)
6698 connection_free_all(sip);
6700 g_free(sip->epid);
6701 sip->epid = NULL;
6703 if (sip->query_data != NULL)
6704 purple_dnsquery_destroy(sip->query_data);
6705 sip->query_data = NULL;
6707 if (sip->srv_query_data != NULL)
6708 purple_srv_cancel(sip->srv_query_data);
6709 sip->srv_query_data = NULL;
6711 if (sip->listen_data != NULL)
6712 purple_network_listen_cancel(sip->listen_data);
6713 sip->listen_data = NULL;
6715 if (sip->gsc != NULL)
6716 purple_ssl_close(sip->gsc);
6717 sip->gsc = NULL;
6719 sipe_auth_free(&sip->registrar);
6720 sipe_auth_free(&sip->proxy);
6722 if (sip->txbuf)
6723 purple_circ_buffer_destroy(sip->txbuf);
6724 sip->txbuf = NULL;
6726 g_free(sip->realhostname);
6727 sip->realhostname = NULL;
6729 g_free(sip->server_version);
6730 sip->server_version = NULL;
6732 if (sip->listenpa)
6733 purple_input_remove(sip->listenpa);
6734 sip->listenpa = 0;
6735 if (sip->tx_handler)
6736 purple_input_remove(sip->tx_handler);
6737 sip->tx_handler = 0;
6738 if (sip->resendtimeout)
6739 purple_timeout_remove(sip->resendtimeout);
6740 sip->resendtimeout = 0;
6741 if (sip->timeouts) {
6742 GSList *entry = sip->timeouts;
6743 while (entry) {
6744 struct scheduled_action *sched_action = entry->data;
6745 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
6746 purple_timeout_remove(sched_action->timeout_handler);
6747 if (sched_action->destroy) {
6748 (*sched_action->destroy)(sched_action->payload);
6750 g_free(sched_action->name);
6751 g_free(sched_action);
6752 entry = entry->next;
6755 g_slist_free(sip->timeouts);
6757 if (sip->allow_events) {
6758 GSList *entry = sip->allow_events;
6759 while (entry) {
6760 g_free(entry->data);
6761 entry = entry->next;
6764 g_slist_free(sip->allow_events);
6766 if (sip->containers) {
6767 GSList *entry = sip->containers;
6768 while (entry) {
6769 free_container((struct sipe_container *)entry->data);
6770 entry = entry->next;
6773 g_slist_free(sip->containers);
6775 if (sip->contact)
6776 g_free(sip->contact);
6777 sip->contact = NULL;
6778 if (sip->regcallid)
6779 g_free(sip->regcallid);
6780 sip->regcallid = NULL;
6782 if (sip->serveraddr)
6783 g_free(sip->serveraddr);
6784 sip->serveraddr = NULL;
6786 if (sip->focus_factory_uri)
6787 g_free(sip->focus_factory_uri);
6788 sip->focus_factory_uri = NULL;
6790 sip->fd = -1;
6791 sip->processing_input = FALSE;
6795 * A callback for g_hash_table_foreach_remove
6797 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
6798 SIPE_UNUSED_PARAMETER gpointer user_data)
6800 sipe_free_buddy((struct sipe_buddy *) buddy);
6802 /* We must return TRUE as the key/value have already been deleted */
6803 return(TRUE);
6806 static void sipe_close(PurpleConnection *gc)
6808 struct sipe_account_data *sip = gc->proto_data;
6810 if (sip) {
6811 /* leave all conversations */
6812 sipe_session_close_all(sip);
6813 sipe_session_remove_all(sip);
6815 if (sip->csta) {
6816 sip_csta_close(sip);
6819 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
6820 /* unsubscribe all */
6821 g_hash_table_foreach(sip->subscriptions, sipe_unsubscribe_cb, sip);
6823 /* unregister */
6824 do_register_exp(sip, 0);
6827 sipe_connection_cleanup(sip);
6828 g_free(sip->sipdomain);
6829 g_free(sip->username);
6830 g_free(sip->password);
6831 g_free(sip->authdomain);
6832 g_free(sip->authuser);
6833 g_free(sip->status);
6835 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
6836 g_hash_table_destroy(sip->buddies);
6837 g_hash_table_destroy(sip->our_publications);
6838 g_hash_table_destroy(sip->subscriptions);
6840 if (sip->groups) {
6841 GSList *entry = sip->groups;
6842 while (entry) {
6843 struct sipe_group *group = entry->data;
6844 g_free(group->name);
6845 g_free(group);
6846 entry = entry->next;
6849 g_slist_free(sip->groups);
6851 if (sip->our_publication_keys) {
6852 GSList *entry = sip->our_publication_keys;
6853 while (entry) {
6854 g_free(entry->data);
6855 entry = entry->next;
6858 g_slist_free(sip->our_publication_keys);
6860 while (sip->transactions)
6861 transactions_remove(sip, sip->transactions->data);
6863 g_free(gc->proto_data);
6864 gc->proto_data = NULL;
6867 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
6868 SIPE_UNUSED_PARAMETER void *user_data)
6870 PurpleAccount *acct = purple_connection_get_account(gc);
6871 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
6872 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
6873 if (conv == NULL)
6874 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
6875 purple_conversation_present(conv);
6876 g_free(id);
6879 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
6880 SIPE_UNUSED_PARAMETER void *user_data)
6883 purple_blist_request_add_buddy(purple_connection_get_account(gc),
6884 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
6887 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
6888 SIPE_UNUSED_PARAMETER struct transaction *trans)
6890 PurpleNotifySearchResults *results;
6891 PurpleNotifySearchColumn *column;
6892 xmlnode *searchResults;
6893 xmlnode *mrow;
6894 int match_count = 0;
6895 gboolean more = FALSE;
6896 gchar *secondary;
6898 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
6900 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6901 if (!searchResults) {
6902 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
6903 return FALSE;
6906 results = purple_notify_searchresults_new();
6908 if (results == NULL) {
6909 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
6910 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
6912 xmlnode_free(searchResults);
6913 return FALSE;
6916 column = purple_notify_searchresults_column_new(_("User name"));
6917 purple_notify_searchresults_column_add(results, column);
6919 column = purple_notify_searchresults_column_new(_("Name"));
6920 purple_notify_searchresults_column_add(results, column);
6922 column = purple_notify_searchresults_column_new(_("Company"));
6923 purple_notify_searchresults_column_add(results, column);
6925 column = purple_notify_searchresults_column_new(_("Country"));
6926 purple_notify_searchresults_column_add(results, column);
6928 column = purple_notify_searchresults_column_new(_("Email"));
6929 purple_notify_searchresults_column_add(results, column);
6931 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
6932 GList *row = NULL;
6934 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
6935 row = g_list_append(row, g_strdup(uri_parts[1]));
6936 g_strfreev(uri_parts);
6938 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
6939 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
6940 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
6941 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
6943 purple_notify_searchresults_row_add(results, row);
6944 match_count++;
6947 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
6948 char *data = xmlnode_get_data_unescaped(mrow);
6949 more = (g_strcasecmp(data, "true") == 0);
6950 g_free(data);
6953 secondary = g_strdup_printf(
6954 dngettext(GETTEXT_PACKAGE,
6955 "Found %d contact%s:",
6956 "Found %d contacts%s:", match_count),
6957 match_count, more ? _(" (more matched your query)") : "");
6959 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6960 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6961 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6963 g_free(secondary);
6964 xmlnode_free(searchResults);
6965 return TRUE;
6968 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6970 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6971 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6972 unsigned i = 0;
6974 do {
6975 PurpleRequestField *field = entries->data;
6976 const char *id = purple_request_field_get_id(field);
6977 const char *value = purple_request_field_string_get_value(field);
6979 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
6981 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6982 } while ((entries = g_list_next(entries)) != NULL);
6983 attrs[i] = NULL;
6985 if (i > 0) {
6986 struct sipe_account_data *sip = gc->proto_data;
6987 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
6988 gchar *query = g_strjoinv(NULL, attrs);
6989 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6990 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
6991 send_soap_request_with_cb(sip, domain_uri, body,
6992 (TransCallback) process_search_contact_response, NULL);
6993 g_free(domain_uri);
6994 g_free(body);
6995 g_free(query);
6998 g_strfreev(attrs);
7001 static void sipe_show_find_contact(PurplePluginAction *action)
7003 PurpleConnection *gc = (PurpleConnection *) action->context;
7004 PurpleRequestFields *fields;
7005 PurpleRequestFieldGroup *group;
7006 PurpleRequestField *field;
7008 fields = purple_request_fields_new();
7009 group = purple_request_field_group_new(NULL);
7010 purple_request_fields_add_group(fields, group);
7012 field = purple_request_field_string_new("givenName", _("First name"), NULL, FALSE);
7013 purple_request_field_group_add_field(group, field);
7014 field = purple_request_field_string_new("sn", _("Last name"), NULL, FALSE);
7015 purple_request_field_group_add_field(group, field);
7016 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
7017 purple_request_field_group_add_field(group, field);
7018 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
7019 purple_request_field_group_add_field(group, field);
7021 purple_request_fields(gc,
7022 _("Search"),
7023 _("Search for a contact"),
7024 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
7025 fields,
7026 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
7027 _("_Cancel"), NULL,
7028 purple_connection_get_account(gc), NULL, NULL, gc);
7031 static void sipe_show_about_plugin(PurplePluginAction *action)
7033 PurpleConnection *gc = (PurpleConnection *) action->context;
7034 const char *txt =
7035 "<b><font size=\"+1\">Sipe " SIPE_VERSION "</font></b><br/>"
7036 "<br/>"
7037 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
7038 "<li> - MS Office Communications Server 2007 (R2)</li><br/>"
7039 "<li> - MS Live Communications Server 2005/2003</li><br/>"
7040 "<li> - Reuters Messaging</li><br/>"
7041 "<br/>"
7042 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
7043 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
7044 "License: GPLv2<br/>"
7045 "<br/>"
7046 "We support users in the following organizations to mention a few:<br/>"
7047 " - CERN<br/>"
7048 " - Reuters Messaging network<br/>"
7049 " - Deutsche Bank<br/>"
7050 " - Merrill Lynch<br/>"
7051 " - Wachovia<br/>"
7052 " - Siemens<br/>"
7053 " - Alcatel-Lucent<br/>"
7054 " - Nokia<br/>"
7055 " - HP<br/>"
7056 "<br/>"
7057 "<b>Authors:</b><br/>"
7058 " - Anibal Avelar<br/>"
7059 " - Gabriel Burt<br/>"
7060 " - Stefan Becker<br/>"
7061 " - pier11<br/>";
7063 purple_notify_formatted(gc, NULL, " ", NULL, txt, NULL, NULL);
7066 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
7067 SIPE_UNUSED_PARAMETER gpointer context)
7069 GList *menu = NULL;
7070 PurplePluginAction *act;
7072 act = purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin);
7073 menu = g_list_prepend(menu, act);
7075 act = purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact);
7076 menu = g_list_prepend(menu, act);
7078 menu = g_list_reverse(menu);
7080 return menu;
7083 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
7087 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
7089 return TRUE;
7093 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
7095 return TRUE;
7099 static char *sipe_status_text(PurpleBuddy *buddy)
7101 struct sipe_account_data *sip;
7102 struct sipe_buddy *sbuddy;
7103 char *text = NULL;
7105 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
7106 if (sip) //happens on pidgin exit
7108 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
7109 if (sbuddy) {
7110 if (!is_empty(sbuddy->activity) && !is_empty(sbuddy->annotation))
7112 text = g_strdup_printf("%s. %s", sbuddy->activity, sbuddy->annotation);
7114 else if (!is_empty(sbuddy->activity))
7116 text = g_strdup(sbuddy->activity);
7118 else
7120 text = g_strdup(sbuddy->annotation);
7125 return text;
7128 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
7130 const PurplePresence *presence = purple_buddy_get_presence(buddy);
7131 const PurpleStatus *status = purple_presence_get_active_status(presence);
7132 struct sipe_account_data *sip;
7133 struct sipe_buddy *sbuddy;
7134 char *annotation = NULL;
7135 char *activity = NULL;
7136 char *meeting_subject = NULL;
7137 char *meeting_location = NULL;
7139 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
7140 if (sip) //happens on pidgin exit
7142 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
7143 if (sbuddy)
7145 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
7146 activity = sbuddy->activity;
7147 meeting_subject = sbuddy->meeting_subject;
7148 meeting_location = sbuddy->meeting_location;
7152 //Layout
7153 if (purple_presence_is_online(presence))
7155 char *tmp = NULL;
7156 const char *status_str = activity && status && strcmp(purple_status_get_id(status), SIPE_STATUS_ID_ONPHONE) ?
7157 (tmp = g_strdup_printf("%s (%s)", purple_status_get_name(status), activity)) :
7158 purple_status_get_name(status);
7160 purple_notify_user_info_add_pair(user_info, _("Status"), status_str);
7161 g_free(tmp);
7163 if (!is_empty(meeting_subject))
7165 purple_notify_user_info_add_pair(user_info, _("Meeting About"), meeting_subject);
7167 if (!is_empty(meeting_location))
7169 purple_notify_user_info_add_pair(user_info, _("Meeting In"), meeting_location);
7172 if (annotation)
7174 /* Tooltip does not know how to handle markup like <br> */
7175 gchar *s = annotation;
7176 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
7177 while ((s = strchr(s, '<')) != NULL) {
7178 if (!g_ascii_strncasecmp(s, "<br>", 4)) {
7179 *s = '\n';
7180 strcpy(s + 1, s + 4);
7182 s++;
7184 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy->name, annotation);
7186 purple_notify_user_info_add_pair(user_info, _("Note"), annotation);
7187 g_free(annotation);
7192 #if PURPLE_VERSION_CHECK(2,5,0)
7193 static GHashTable *
7194 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
7196 GHashTable *table;
7197 table = g_hash_table_new(g_str_hash, g_str_equal);
7198 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
7199 return table;
7201 #endif
7203 static PurpleBuddy *
7204 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
7206 PurpleBuddy *clone;
7207 const gchar *server_alias, *email;
7208 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
7210 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
7212 purple_blist_add_buddy(clone, NULL, group, NULL);
7214 server_alias = purple_buddy_get_server_alias(buddy);
7215 if (server_alias) {
7216 purple_blist_server_alias_buddy(clone, server_alias);
7219 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
7220 if (email) {
7221 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
7224 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
7225 //for UI to update;
7226 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
7227 return clone;
7230 static void
7231 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
7233 PurpleBuddy *buddy, *b;
7234 PurpleConnection *gc;
7235 PurpleGroup * group = purple_find_group(group_name);
7237 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
7239 buddy = (PurpleBuddy *)node;
7241 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
7242 gc = purple_account_get_connection(buddy->account);
7244 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
7245 if (!b){
7246 b = purple_blist_add_buddy_clone(group, buddy);
7249 sipe_group_buddy(gc, buddy->name, NULL, group_name);
7252 static void
7253 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
7255 struct sipe_account_data *sip = buddy->account->gc->proto_data;
7257 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy->name);
7259 /* 2007+ conference */
7260 if (sip->ocs2007)
7262 sipe_conf_add(sip, buddy->name);
7264 else /* 2005- multiparty chat */
7266 gchar *self = sip_uri_self(sip);
7267 struct sip_session *session;
7269 session = sipe_session_add_chat(sip);
7270 session->chat_title = sipe_chat_get_name(session->callid);
7271 session->roster_manager = g_strdup(self);
7273 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, session->chat_title);
7274 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
7275 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
7276 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
7278 g_free(self);
7282 static gboolean
7283 sipe_is_election_finished(struct sip_session *session)
7285 gboolean res = TRUE;
7287 SIPE_DIALOG_FOREACH {
7288 if (dialog->election_vote == 0) {
7289 res = FALSE;
7290 break;
7292 } SIPE_DIALOG_FOREACH_END;
7294 if (res) {
7295 session->is_voting_in_progress = FALSE;
7297 return res;
7300 static void
7301 sipe_election_start(struct sipe_account_data *sip,
7302 struct sip_session *session)
7304 int election_timeout;
7306 if (session->is_voting_in_progress) {
7307 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
7308 return;
7309 } else {
7310 session->is_voting_in_progress = TRUE;
7312 session->bid = rand();
7314 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
7316 SIPE_DIALOG_FOREACH {
7317 /* reset election_vote for each chat participant */
7318 dialog->election_vote = 0;
7320 /* send RequestRM to each chat participant*/
7321 sipe_send_election_request_rm(sip, dialog, session->bid);
7322 } SIPE_DIALOG_FOREACH_END;
7324 election_timeout = 15; /* sec */
7325 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
7329 * @param who a URI to whom to invite to chat
7331 void
7332 sipe_invite_to_chat(struct sipe_account_data *sip,
7333 struct sip_session *session,
7334 const gchar *who)
7336 /* a conference */
7337 if (session->focus_uri)
7339 sipe_invite_conf(sip, session, who);
7341 else /* a multi-party chat */
7343 gchar *self = sip_uri_self(sip);
7344 if (session->roster_manager) {
7345 if (!strcmp(session->roster_manager, self)) {
7346 sipe_invite(sip, session, who, NULL, NULL, FALSE);
7347 } else {
7348 sipe_refer(sip, session, who);
7350 } else {
7351 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
7353 session->pending_invite_queue = slist_insert_unique_sorted(
7354 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
7356 sipe_election_start(sip, session);
7358 g_free(self);
7362 void
7363 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
7364 struct sip_session *session)
7366 gchar *invitee;
7367 GSList *entry = session->pending_invite_queue;
7369 while (entry) {
7370 invitee = entry->data;
7371 sipe_invite_to_chat(sip, session, invitee);
7372 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
7373 g_free(invitee);
7377 static void
7378 sipe_election_result(struct sipe_account_data *sip,
7379 void *sess)
7381 struct sip_session *session = (struct sip_session *)sess;
7382 gchar *rival;
7383 gboolean has_won = TRUE;
7385 if (session->roster_manager) {
7386 purple_debug_info("sipe",
7387 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
7388 return;
7391 session->is_voting_in_progress = FALSE;
7393 SIPE_DIALOG_FOREACH {
7394 if (dialog->election_vote < 0) {
7395 has_won = FALSE;
7396 rival = dialog->with;
7397 break;
7399 } SIPE_DIALOG_FOREACH_END;
7401 if (has_won) {
7402 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
7404 session->roster_manager = sip_uri_self(sip);
7406 SIPE_DIALOG_FOREACH {
7407 /* send SetRM to each chat participant*/
7408 sipe_send_election_set_rm(sip, dialog);
7409 } SIPE_DIALOG_FOREACH_END;
7410 } else {
7411 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
7413 session->bid = 0;
7415 sipe_process_pending_invite_queue(sip, session);
7419 * For 2007+ conference only.
7421 static void
7422 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
7424 struct sipe_account_data *sip = buddy->account->gc->proto_data;
7425 struct sip_session *session;
7427 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
7428 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title);
7430 session = sipe_session_find_chat_by_title(sip, chat_title);
7432 sipe_conf_modify_user_role(sip, session, buddy->name);
7436 * For 2007+ conference only.
7438 static void
7439 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
7441 struct sipe_account_data *sip = buddy->account->gc->proto_data;
7442 struct sip_session *session;
7444 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy->name);
7445 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title);
7447 session = sipe_session_find_chat_by_title(sip, chat_title);
7449 sipe_conf_delete_user(sip, session, buddy->name);
7452 static void
7453 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
7455 struct sipe_account_data *sip = buddy->account->gc->proto_data;
7456 struct sip_session *session;
7458 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy->name);
7459 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title);
7461 session = sipe_session_find_chat_by_title(sip, chat_title);
7463 sipe_invite_to_chat(sip, session, buddy->name);
7466 static void
7467 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
7469 struct sipe_account_data *sip = buddy->account->gc->proto_data;
7471 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy->name);
7472 if (phone) {
7473 char *tel_uri = sip_to_tel_uri(phone);
7475 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri ? tel_uri : "");
7476 sip_csta_make_call(sip, tel_uri);
7478 g_free(tel_uri);
7482 static void
7483 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
7485 const gchar *email;
7486 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
7488 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
7489 if (email)
7491 char *mailto = g_strdup_printf("mailto:%s", email);
7492 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
7493 #ifndef _WIN32
7495 pid_t pid;
7496 char *const parmList[] = {mailto, NULL};
7497 if ((pid = fork()) == -1)
7499 purple_debug_info("sipe", "fork() error\n");
7501 else if (pid == 0)
7503 execvp("xdg-email", parmList);
7504 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
7507 #else
7509 BOOL ret;
7510 _flushall();
7511 errno = 0;
7512 //@TODO resolve env variable %WINDIR% first
7513 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
7514 if (errno)
7516 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
7519 #endif
7521 g_free(mailto);
7523 else
7525 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
7530 * A menu which appear when right-clicking on buddy in contact list.
7532 static GList *
7533 sipe_buddy_menu(PurpleBuddy *buddy)
7535 PurpleBlistNode *g_node;
7536 PurpleGroup *group, *gr_parent;
7537 PurpleMenuAction *act;
7538 GList *menu = NULL;
7539 GList *menu_groups = NULL;
7540 struct sipe_account_data *sip = buddy->account->gc->proto_data;
7541 const char *email;
7542 const char *phone;
7543 const char *phone_disp_str;
7544 gchar *self = sip_uri_self(sip);
7546 SIPE_SESSION_FOREACH {
7547 if (g_ascii_strcasecmp(self, buddy->name) && session->chat_title && session->conv)
7549 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
7551 PurpleConvChatBuddyFlags flags;
7552 PurpleConvChatBuddyFlags flags_us;
7554 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
7555 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
7556 if (session->focus_uri
7557 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
7558 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
7560 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
7561 act = purple_menu_action_new(label,
7562 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
7563 session->chat_title, NULL);
7564 g_free(label);
7565 menu = g_list_prepend(menu, act);
7568 if (session->focus_uri
7569 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
7571 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
7572 act = purple_menu_action_new(label,
7573 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
7574 session->chat_title, NULL);
7575 g_free(label);
7576 menu = g_list_prepend(menu, act);
7579 else
7581 if (!session->focus_uri
7582 || (session->focus_uri && !session->locked))
7584 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
7585 act = purple_menu_action_new(label,
7586 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
7587 session->chat_title, NULL);
7588 g_free(label);
7589 menu = g_list_prepend(menu, act);
7593 } SIPE_SESSION_FOREACH_END;
7595 act = purple_menu_action_new(_("New chat"),
7596 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
7597 NULL, NULL);
7598 menu = g_list_prepend(menu, act);
7600 if (sip->csta && !sip->csta->line_status) {
7601 gchar *tmp = NULL;
7602 /* work phone */
7603 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
7604 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
7605 if (phone) {
7606 gchar *label = g_strdup_printf(_("Work %s"),
7607 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
7608 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
7609 g_free(tmp);
7610 tmp = NULL;
7611 g_free(label);
7612 menu = g_list_prepend(menu, act);
7615 /* mobile phone */
7616 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
7617 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
7618 if (phone) {
7619 gchar *label = g_strdup_printf(_("Mobile %s"),
7620 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
7621 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
7622 g_free(tmp);
7623 tmp = NULL;
7624 g_free(label);
7625 menu = g_list_prepend(menu, act);
7628 /* home phone */
7629 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
7630 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
7631 if (phone) {
7632 gchar *label = g_strdup_printf(_("Home %s"),
7633 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
7634 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
7635 g_free(tmp);
7636 tmp = NULL;
7637 g_free(label);
7638 menu = g_list_prepend(menu, act);
7641 /* other phone */
7642 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
7643 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
7644 if (phone) {
7645 gchar *label = g_strdup_printf(_("Other %s"),
7646 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
7647 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
7648 g_free(tmp);
7649 tmp = NULL;
7650 g_free(label);
7651 menu = g_list_prepend(menu, act);
7654 /* custom1 phone */
7655 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
7656 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
7657 if (phone) {
7658 gchar *label = g_strdup_printf(_("Custom1 %s"),
7659 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
7660 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
7661 g_free(tmp);
7662 tmp = NULL;
7663 g_free(label);
7664 menu = g_list_prepend(menu, act);
7668 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
7669 if (email) {
7670 act = purple_menu_action_new(_("Send email..."),
7671 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
7672 NULL, NULL);
7673 menu = g_list_prepend(menu, act);
7676 gr_parent = purple_buddy_get_group(buddy);
7677 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
7678 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
7679 continue;
7681 group = (PurpleGroup *)g_node;
7682 if (group == gr_parent)
7683 continue;
7685 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
7686 continue;
7688 act = purple_menu_action_new(purple_group_get_name(group),
7689 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
7690 group->name, NULL);
7691 menu_groups = g_list_prepend(menu_groups, act);
7693 menu_groups = g_list_reverse(menu_groups);
7695 act = purple_menu_action_new(_("Copy to"),
7696 NULL,
7697 NULL, menu_groups);
7698 menu = g_list_prepend(menu, act);
7699 menu = g_list_reverse(menu);
7701 g_free(self);
7702 return menu;
7705 static void
7706 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
7708 struct sipe_account_data *sip = chat->account->gc->proto_data;
7709 struct sip_session *session;
7711 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
7712 sipe_conf_modify_conference_lock(sip, session, locked);
7715 static void
7716 sipe_chat_menu_unlock_cb(PurpleChat *chat)
7718 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
7719 sipe_conf_modify_lock(chat, FALSE);
7722 static void
7723 sipe_chat_menu_lock_cb(PurpleChat *chat)
7725 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
7726 sipe_conf_modify_lock(chat, TRUE);
7729 static GList *
7730 sipe_chat_menu(PurpleChat *chat)
7732 PurpleMenuAction *act;
7733 PurpleConvChatBuddyFlags flags_us;
7734 GList *menu = NULL;
7735 struct sipe_account_data *sip = chat->account->gc->proto_data;
7736 struct sip_session *session;
7737 gchar *self;
7739 session = sipe_session_find_chat_by_title(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
7740 if (!session) return NULL;
7742 self = sip_uri_self(sip);
7743 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
7745 if (session->focus_uri
7746 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
7748 if (session->locked) {
7749 act = purple_menu_action_new(_("Unlock"),
7750 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
7751 NULL, NULL);
7752 menu = g_list_prepend(menu, act);
7753 } else {
7754 act = purple_menu_action_new(_("Lock"),
7755 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
7756 NULL, NULL);
7757 menu = g_list_prepend(menu, act);
7761 menu = g_list_reverse(menu);
7763 g_free(self);
7764 return menu;
7767 static GList *
7768 sipe_blist_node_menu(PurpleBlistNode *node)
7770 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
7771 return sipe_buddy_menu((PurpleBuddy *) node);
7772 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7773 return sipe_chat_menu((PurpleChat *)node);
7774 } else {
7775 return NULL;
7779 static gboolean
7780 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
7782 gboolean ret = TRUE;
7783 char *uri = trans->payload->data;
7785 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
7786 PurpleBuddy *pbuddy;
7787 struct sipe_buddy *sbuddy;
7788 const char *alias;
7789 char *device_name = NULL;
7790 char *server_alias = NULL;
7791 char *phone_number = NULL;
7792 char *email = NULL;
7793 const char *site;
7795 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri, sip->username);
7797 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
7798 alias = purple_buddy_get_local_alias(pbuddy);
7800 if (sip)
7802 //will query buddy UA's capabilities and send answer to log
7803 sipe_options_request(sip, uri);
7805 sbuddy = g_hash_table_lookup(sip->buddies, uri);
7806 if (sbuddy)
7808 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
7812 if (msg->response != 200) {
7813 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
7814 } else {
7815 xmlnode *searchResults;
7816 xmlnode *mrow;
7818 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
7819 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
7820 if (!searchResults) {
7821 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
7822 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
7823 const char *value;
7824 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
7825 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
7826 phone_number = g_strdup(xmlnode_get_attrib(mrow, "phone"));
7828 /* For 2007 system we will take this from ContactCard -
7829 * it has cleaner tel: URIs at least
7831 if (!sip->ocs2007) {
7832 char *tel_uri = sip_to_tel_uri(phone_number);
7833 /* trims its parameters, so call first */
7834 sipe_update_user_info(sip, uri, ALIAS_PROP, server_alias);
7835 sipe_update_user_info(sip, uri, EMAIL_PROP, email);
7836 sipe_update_user_info(sip, uri, PHONE_PROP, tel_uri);
7837 sipe_update_user_info(sip, uri, PHONE_DISPLAY_PROP, phone_number);
7838 g_free(tel_uri);
7841 if (server_alias && strlen(server_alias) > 0) {
7842 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7844 if ((value = xmlnode_get_attrib(mrow, "title")) && strlen(value) > 0) {
7845 purple_notify_user_info_add_pair(info, _("Job title"), value);
7847 if ((value = xmlnode_get_attrib(mrow, "office")) && strlen(value) > 0) {
7848 purple_notify_user_info_add_pair(info, _("Office"), value);
7850 if (phone_number && strlen(phone_number) > 0) {
7851 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
7853 if ((value = xmlnode_get_attrib(mrow, "company")) && strlen(value) > 0) {
7854 purple_notify_user_info_add_pair(info, _("Company"), value);
7856 if ((value = xmlnode_get_attrib(mrow, "city")) && strlen(value) > 0) {
7857 purple_notify_user_info_add_pair(info, _("City"), value);
7859 if ((value = xmlnode_get_attrib(mrow, "state")) && strlen(value) > 0) {
7860 purple_notify_user_info_add_pair(info, _("State"), value);
7862 if ((value = xmlnode_get_attrib(mrow, "country")) && strlen(value) > 0) {
7863 purple_notify_user_info_add_pair(info, _("Country"), value);
7865 if (email && strlen(email) > 0) {
7866 purple_notify_user_info_add_pair(info, _("E-Mail address"), email);
7870 xmlnode_free(searchResults);
7873 purple_notify_user_info_add_section_break(info);
7875 if (!server_alias || !strcmp("", server_alias)) {
7876 g_free(server_alias);
7877 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
7878 if (server_alias) {
7879 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7883 /* present alias if it differs from server alias */
7884 if (alias && (!server_alias || strcmp(alias, server_alias)))
7886 purple_notify_user_info_add_pair(info, _("Alias"), alias);
7889 if (!email || !strcmp("", email)) {
7890 g_free(email);
7891 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
7892 if (email) {
7893 purple_notify_user_info_add_pair(info, _("E-Mail address"), email);
7897 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
7898 if (site) {
7899 purple_notify_user_info_add_pair(info, _("Site"), site);
7902 if (device_name) {
7903 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7906 /* show a buddy's user info in a nice dialog box */
7907 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7908 uri, /* buddy's URI */
7909 info, /* body */
7910 NULL, /* callback called when dialog closed */
7911 NULL); /* userdata for callback */
7913 g_free(phone_number);
7914 g_free(server_alias);
7915 g_free(email);
7916 g_free(device_name);
7918 return ret;
7922 * AD search first, LDAP based
7924 static void sipe_get_info(PurpleConnection *gc, const char *username)
7926 struct sipe_account_data *sip = gc->proto_data;
7927 gchar *domain_uri = sip_uri_from_name(sip->sipdomain);
7928 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7929 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7930 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7932 payload->destroy = g_free;
7933 payload->data = g_strdup(username);
7935 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
7936 send_soap_request_with_cb(sip, domain_uri, body,
7937 (TransCallback) process_get_info_response, payload);
7938 g_free(domain_uri);
7939 g_free(body);
7940 g_free(row);
7943 static PurplePlugin *my_protocol = NULL;
7945 static PurplePluginProtocolInfo prpl_info =
7947 OPT_PROTO_CHAT_TOPIC,
7948 NULL, /* user_splits */
7949 NULL, /* protocol_options */
7950 NO_BUDDY_ICONS, /* icon_spec */
7951 sipe_list_icon, /* list_icon */
7952 NULL, /* list_emblems */
7953 sipe_status_text, /* status_text */
7954 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
7955 sipe_status_types, /* away_states */
7956 sipe_blist_node_menu, /* blist_node_menu */
7957 NULL, /* chat_info */
7958 NULL, /* chat_info_defaults */
7959 sipe_login, /* login */
7960 sipe_close, /* close */
7961 sipe_im_send, /* send_im */
7962 NULL, /* set_info */ // TODO maybe
7963 sipe_send_typing, /* send_typing */
7964 sipe_get_info, /* get_info */
7965 sipe_set_status, /* set_status */
7966 sipe_set_idle, /* set_idle */
7967 NULL, /* change_passwd */
7968 sipe_add_buddy, /* add_buddy */
7969 NULL, /* add_buddies */
7970 sipe_remove_buddy, /* remove_buddy */
7971 NULL, /* remove_buddies */
7972 sipe_add_permit, /* add_permit */
7973 sipe_add_deny, /* add_deny */
7974 sipe_add_deny, /* rem_permit */
7975 sipe_add_permit, /* rem_deny */
7976 dummy_permit_deny, /* set_permit_deny */
7977 NULL, /* join_chat */
7978 NULL, /* reject_chat */
7979 NULL, /* get_chat_name */
7980 sipe_chat_invite, /* chat_invite */
7981 sipe_chat_leave, /* chat_leave */
7982 NULL, /* chat_whisper */
7983 sipe_chat_send, /* chat_send */
7984 sipe_keep_alive, /* keepalive */
7985 NULL, /* register_user */
7986 NULL, /* get_cb_info */ // deprecated
7987 NULL, /* get_cb_away */ // deprecated
7988 sipe_alias_buddy, /* alias_buddy */
7989 sipe_group_buddy, /* group_buddy */
7990 sipe_rename_group, /* rename_group */
7991 NULL, /* buddy_free */
7992 sipe_convo_closed, /* convo_closed */
7993 purple_normalize_nocase, /* normalize */
7994 NULL, /* set_buddy_icon */
7995 sipe_remove_group, /* remove_group */
7996 NULL, /* get_cb_real_name */ // TODO?
7997 NULL, /* set_chat_topic */
7998 NULL, /* find_blist_chat */
7999 NULL, /* roomlist_get_list */
8000 NULL, /* roomlist_cancel */
8001 NULL, /* roomlist_expand_category */
8002 NULL, /* can_receive_file */
8003 NULL, /* send_file */
8004 NULL, /* new_xfer */
8005 NULL, /* offline_message */
8006 NULL, /* whiteboard_prpl_ops */
8007 sipe_send_raw, /* send_raw */
8008 NULL, /* roomlist_room_serialize */
8009 NULL, /* unregister_user */
8010 NULL, /* send_attention */
8011 NULL, /* get_attention_types */
8012 #if !PURPLE_VERSION_CHECK(2,5,0)
8013 /* Backward compatibility when compiling against 2.4.x API */
8014 (void (*)(void)) /* _purple_reserved4 */
8015 #endif
8016 sizeof(PurplePluginProtocolInfo), /* struct_size */
8017 #if PURPLE_VERSION_CHECK(2,5,0)
8018 sipe_get_account_text_table, /* get_account_text_table */
8019 #if PURPLE_VERSION_CHECK(2,6,0)
8020 NULL, /* initiate_media */
8021 NULL, /* get_media_caps */
8022 #endif
8023 #endif
8027 static PurplePluginInfo info = {
8028 PURPLE_PLUGIN_MAGIC,
8029 PURPLE_MAJOR_VERSION,
8030 PURPLE_MINOR_VERSION,
8031 PURPLE_PLUGIN_PROTOCOL, /**< type */
8032 NULL, /**< ui_requirement */
8033 0, /**< flags */
8034 NULL, /**< dependencies */
8035 PURPLE_PRIORITY_DEFAULT, /**< priority */
8036 "prpl-sipe", /**< id */
8037 "Office Communicator", /**< name */
8038 SIPE_VERSION, /**< version */
8039 "Microsoft Office Communicator Protocol Plugin", /**< summary */
8040 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
8041 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
8042 "Anibal Avelar <avelar@gmail.com>, " /**< author */
8043 "Gabriel Burt <gburt@novell.com>, " /**< author */
8044 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
8045 "pier11 <pier11@operamail.com>", /**< author */
8046 "http://sipe.sourceforge.net/", /**< homepage */
8047 sipe_plugin_load, /**< load */
8048 sipe_plugin_unload, /**< unload */
8049 sipe_plugin_destroy, /**< destroy */
8050 NULL, /**< ui_info */
8051 &prpl_info, /**< extra_info */
8052 NULL,
8053 sipe_actions,
8054 NULL,
8055 NULL,
8056 NULL,
8057 NULL
8060 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
8062 GList *entry;
8064 entry = prpl_info.protocol_options;
8065 while (entry) {
8066 purple_account_option_destroy(entry->data);
8067 entry = g_list_delete_link(entry, entry);
8069 prpl_info.protocol_options = NULL;
8071 entry = prpl_info.user_splits;
8072 while (entry) {
8073 purple_account_user_split_destroy(entry->data);
8074 entry = g_list_delete_link(entry, entry);
8076 prpl_info.user_splits = NULL;
8079 static void init_plugin(PurplePlugin *plugin)
8081 PurpleAccountUserSplit *split;
8082 PurpleAccountOption *option;
8084 srand(time(NULL));
8086 #ifdef ENABLE_NLS
8087 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
8088 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
8089 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
8090 textdomain(GETTEXT_PACKAGE);
8091 #endif
8093 purple_plugin_register(plugin);
8095 split = purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL, ',');
8096 purple_account_user_split_set_reverse(split, FALSE);
8097 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
8099 option = purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
8100 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8102 option = purple_account_option_list_new(_("Connection type"), "transport", NULL);
8103 purple_account_option_add_list_item(option, _("Auto"), "auto");
8104 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
8105 purple_account_option_add_list_item(option, _("TCP"), "tcp");
8106 purple_account_option_add_list_item(option, _("UDP"), "udp");
8107 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8109 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
8110 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
8112 option = purple_account_option_string_new(_("User Agent"), "useragent", "");
8113 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8115 #ifdef USE_KERBEROS
8116 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
8117 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8119 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
8120 * No login/password is taken into account if this option present,
8121 * instead used default credentials stored in OS.
8123 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
8124 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
8125 #endif
8126 my_protocol = plugin;
8129 /* I had to redefined the function for it load, but works */
8130 gboolean purple_init_plugin(PurplePlugin *plugin){
8131 plugin->info = &(info);
8132 init_plugin((plugin));
8133 sipe_plugin_load((plugin));
8134 return purple_plugin_register(plugin);
8138 Local Variables:
8139 mode: c
8140 c-file-style: "bsd"
8141 indent-tabs-mode: t
8142 tab-width: 8
8143 End: