Revert "Display Name update from AD if needed"
[siplcs.git] / src / sipe.c
blob796b1eeb141edb0a0505ee09fa90d2d1cb54f97d
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2007 Anibal Avelar <avelar@gmail.com>
8 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
10 * ***
11 * Thanks to Google's Summer of Code Program and the helpful mentors
12 * ***
14 * Session-based SIP MESSAGE documentation:
15 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 #ifndef _WIN32
33 #include <sys/socket.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
36 #include <netinet/in.h>
37 #include <net/if.h>
38 #ifdef ENABLE_NLS
39 # include <libintl.h>
40 # define _(String) ((const char *) gettext (String))
41 #else
42 # define _(String) ((const char *) (String))
43 #endif /* ENABLE_NLS */
44 #else
45 #ifdef _DLL
46 #define _WS2TCPIP_H_
47 #define _WINSOCK2API_
48 #define _LIBC_INTERNAL_
49 #endif /* _DLL */
51 #include "internal.h"
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <glib.h>
61 #include "accountopt.h"
62 #include "blist.h"
63 #include "conversation.h"
64 #include "dnsquery.h"
65 #include "debug.h"
66 #include "notify.h"
67 #include "privacy.h"
68 #include "prpl.h"
69 #include "plugin.h"
70 #include "util.h"
71 #include "version.h"
72 #include "network.h"
73 #include "xmlnode.h"
74 #include "mime.h"
76 #include "sipe.h"
77 #include "sip-ntlm.h"
78 #ifdef USE_KERBEROS
79 #include "sipkrb5.h"
80 #endif /*USE_KERBEROS*/
82 #include "sipmsg.h"
83 #include "sipe-sign.h"
84 #include "dnssrv.h"
85 #include "request.h"
87 /* Keep in sync with sipe_transport_type! */
88 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
89 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
91 /* Status identifiers (see also: sipe_status_types()) */
92 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
93 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
94 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
95 /* PURPLE_STATUS_UNAVAILABLE: */
96 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
97 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
98 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
99 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
100 /* PURPLE_STATUS_AWAY: */
101 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
102 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
103 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
104 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
105 /* ??? PURPLE_STATUS_MOBILE */
106 /* ??? PURPLE_STATUS_TUNE */
108 static char *gentag()
110 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
113 static gchar *get_epid(struct sipe_account_data *sip)
115 if (!sip->epid) {
116 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
118 return g_strdup(sip->epid);
121 static char *genbranch()
123 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
124 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
125 rand() & 0xFFFF, rand() & 0xFFFF);
128 static char *gencallid()
130 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
131 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
132 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
133 rand() & 0xFFFF, rand() & 0xFFFF);
136 static gchar *find_tag(const gchar *hdr)
138 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
139 if (!tag) {
140 // In case it's at the end and there's no trailing ;
141 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
143 return tag;
147 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
149 return "sipe";
152 static void sipe_plugin_destroy(PurplePlugin *plugin);
154 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
156 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
157 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
158 gpointer data);
160 static void sipe_close(PurpleConnection *gc);
162 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name);
163 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip);
164 static void send_presence_status(struct sipe_account_data *sip);
166 static void sendout_pkt(PurpleConnection *gc, const char *buf);
168 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
170 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
171 if (timeout != NULL) {
172 sscanf(timeout, "%u", &sip->keepalive_timeout);
173 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
174 sip->keepalive_timeout);
176 g_free(timeout);
179 static void sipe_keep_alive(PurpleConnection *gc)
181 struct sipe_account_data *sip = gc->proto_data;
182 if (sip->transport == SIPE_TRANSPORT_UDP) {
183 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
184 gchar buf[2] = {0, 0};
185 purple_debug_info("sipe", "sending keep alive\n");
186 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
187 } else {
188 time_t now = time(NULL);
189 if ((sip->keepalive_timeout > 0) &&
190 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
191 #if PURPLE_VERSION_CHECK(2,4,0)
192 && ((now - gc->last_received) >= sip->keepalive_timeout)
193 #endif
195 purple_debug_info("sipe", "sending keep alive\n");
196 sendout_pkt(gc, "\r\n\r\n");
197 sip->last_keepalive = now;
202 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
204 struct sip_connection *ret = NULL;
205 GSList *entry = sip->openconns;
206 while (entry) {
207 ret = entry->data;
208 if (ret->fd == fd) return ret;
209 entry = entry->next;
211 return NULL;
214 static void sipe_auth_free(struct sip_auth *auth)
216 g_free(auth->nonce);
217 auth->nonce = NULL;
218 g_free(auth->opaque);
219 auth->opaque = NULL;
220 g_free(auth->realm);
221 auth->realm = NULL;
222 g_free(auth->target);
223 auth->target = NULL;
224 g_free(auth->digest_session_key);
225 auth->digest_session_key = NULL;
226 g_free(auth->ntlm_key);
227 auth->ntlm_key = NULL;
228 auth->type = AUTH_TYPE_UNSET;
229 auth->retries = 0;
230 auth->expires = 0;
233 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
235 struct sip_connection *ret = g_new0(struct sip_connection, 1);
236 ret->fd = fd;
237 sip->openconns = g_slist_append(sip->openconns, ret);
238 return ret;
241 static void connection_remove(struct sipe_account_data *sip, int fd)
243 struct sip_connection *conn = connection_find(sip, fd);
244 if (conn) {
245 sip->openconns = g_slist_remove(sip->openconns, conn);
246 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
247 g_free(conn->inbuf);
248 g_free(conn);
252 static void connection_free_all(struct sipe_account_data *sip)
254 struct sip_connection *ret = NULL;
255 GSList *entry = sip->openconns;
256 while (entry) {
257 ret = entry->data;
258 connection_remove(sip, ret->fd);
259 entry = sip->openconns;
263 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
265 const gchar *method = msg->method;
266 const gchar *target = msg->target;
267 gchar noncecount[9];
268 gchar *response;
269 gchar *ret;
270 gchar *tmp = NULL;
271 const char *authdomain = sip->authdomain;
272 const char *authuser = sip->authuser;
273 //const char *krb5_realm;
274 const char *host;
275 //gchar *krb5_token = NULL;
277 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
278 // and do error checking
280 // KRB realm should always be uppercase
281 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
283 if (sip->realhostname) {
284 host = sip->realhostname;
285 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
286 host = purple_account_get_string(sip->account, "proxy", "");
287 } else {
288 host = sip->sipdomain;
291 /*gboolean new_auth = krb5_auth.gss_context == NULL;
292 if (new_auth) {
293 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
296 if (new_auth || force_reauth) {
297 krb5_token = krb5_auth.base64_token;
300 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
301 krb5_token = krb5_auth.base64_token;*/
303 if (!authdomain) {
304 authdomain = "";
307 if (!authuser || strlen(authuser) < 1) {
308 authuser = sip->username;
311 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
312 sprintf(noncecount, "%08d", auth->nc++);
313 response = purple_cipher_http_digest_calculate_response(
314 "md5", method, target, NULL, NULL,
315 auth->nonce, noncecount, NULL, auth->digest_session_key);
316 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
318 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
319 g_free(response);
320 return ret;
321 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
322 // If we have a signature for the message, include that
323 if (msg->signature) {
324 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
325 return tmp;
328 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
329 const gchar *ntlm_key;
330 gchar *gssapi_data;
331 #if GLIB_CHECK_VERSION(2,8,0)
332 const gchar * hostname = g_get_host_name();
333 #else
334 static char hostname[256];
335 int ret = gethostname(hostname, sizeof(hostname));
336 hostname[sizeof(hostname) - 1] = '\0';
337 if (ret == -1 || hostname[0] == '\0') {
338 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
339 g_strerror(errno);
340 strcpy(hostname, "localhost");
342 #endif
343 /*const gchar * hostname = purple_get_host_name();*/
345 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
346 auth->ntlm_key = (gchar *)ntlm_key;
347 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
348 g_free(gssapi_data);
349 return tmp;
352 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
353 return tmp;
354 } else if (auth->type == AUTH_TYPE_KERBEROS) {
355 /* Kerberos */
356 if (auth->nc == 3) {
357 /*if (new_auth || force_reauth) {
358 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
359 if (auth->opaque) {
360 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, krb5_token);
361 } else {
362 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
364 } else {
365 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
366 gchar * mic = "MICTODO";
367 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
368 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
369 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
370 //auth->opaque ? auth->opaque : "", auth->target);
371 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
372 //g_free(mic);
374 return tmp;
376 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
379 sprintf(noncecount, "%08d", auth->nc++);
380 response = purple_cipher_http_digest_calculate_response(
381 "md5", method, target, NULL, NULL,
382 auth->nonce, noncecount, NULL, auth->digest_session_key);
383 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
385 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
386 g_free(response);
387 return ret;
390 static char *parse_attribute(const char *attrname, const char *source)
392 const char *tmp, *tmp2;
393 char *retval = NULL;
394 int len = strlen(attrname);
396 if (!strncmp(source, attrname, len)) {
397 tmp = source + len;
398 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
399 if (tmp2)
400 retval = g_strndup(tmp, tmp2 - tmp);
401 else
402 retval = g_strdup(tmp);
405 return retval;
408 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
410 int i = 0;
411 const char *authuser;
412 char *tmp;
413 gchar **parts;
414 //const char *krb5_realm;
415 //const char *host;
417 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
418 // and do error checking
420 // KRB realm should always be uppercase
421 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
423 if (sip->realhostname) {
424 host = sip->realhostname;
425 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
426 host = purple_account_get_string(sip->account, "proxy", "");
427 } else {
428 host = sip->sipdomain;
431 authuser = sip->authuser;
433 if (!authuser || strlen(authuser) < 1) {
434 authuser = sip->username;
437 if (!hdr) {
438 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
439 return;
442 if (!g_strncasecmp(hdr, "NTLM", 4)) {
443 auth->type = AUTH_TYPE_NTLM;
444 parts = g_strsplit(hdr+5, "\", ", 0);
445 i = 0;
446 while (parts[i]) {
447 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
448 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
449 g_free(auth->nonce);
450 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
451 g_free(tmp);
453 if ((tmp = parse_attribute("targetname=\"",
454 parts[i]))) {
455 g_free(auth->target);
456 auth->target = tmp;
458 else if ((tmp = parse_attribute("realm=\"",
459 parts[i]))) {
460 g_free(auth->realm);
461 auth->realm = tmp;
463 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
464 g_free(auth->opaque);
465 auth->opaque = tmp;
467 i++;
469 g_strfreev(parts);
470 auth->nc = 1;
471 if (!strstr(hdr, "gssapi-data")) {
472 auth->nc = 1;
473 } else {
474 auth->nc = 3;
476 return;
479 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
480 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
481 auth->type = AUTH_TYPE_KERBEROS;
482 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
483 parts = g_strsplit(hdr+9, "\", ", 0);
484 i = 0;
485 while (parts[i]) {
486 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
487 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
488 /*if (krb5_auth.gss_context == NULL) {
489 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
491 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
492 g_free(tmp);
494 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
495 g_free(auth->target);
496 auth->target = tmp;
497 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
498 g_free(auth->realm);
499 auth->realm = tmp;
500 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
501 g_free(auth->opaque);
502 auth->opaque = tmp;
504 i++;
506 g_strfreev(parts);
507 auth->nc = 3;
508 return;
511 auth->type = AUTH_TYPE_DIGEST;
512 parts = g_strsplit(hdr, " ", 0);
513 while (parts[i]) {
514 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
515 g_free(auth->nonce);
516 auth->nonce = tmp;
518 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
519 g_free(auth->realm);
520 auth->realm = tmp;
522 i++;
524 g_strfreev(parts);
526 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
527 if (auth->realm) {
528 g_free(auth->digest_session_key);
529 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
530 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
532 auth->nc = 1;
536 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
538 PurpleConnection *gc = data;
539 struct sipe_account_data *sip = gc->proto_data;
540 gsize max_write;
541 gssize written;
543 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
545 if (max_write == 0) {
546 if (sip->tx_handler != 0){
547 purple_input_remove(sip->tx_handler);
548 sip->tx_handler = 0;
550 return;
553 written = write(sip->fd, sip->txbuf->outptr, max_write);
555 if (written < 0 && errno == EAGAIN)
556 written = 0;
557 else if (written <= 0) {
558 /*TODO: do we really want to disconnect on a failure to write?*/
559 purple_connection_error(gc, _("Could not write"));
560 return;
563 purple_circ_buffer_mark_read(sip->txbuf, written);
566 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
568 PurpleConnection *gc = data;
569 struct sipe_account_data *sip = gc->proto_data;
570 gsize max_write;
571 gssize written;
573 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
575 if (max_write == 0) {
576 if (sip->tx_handler != 0) {
577 purple_input_remove(sip->tx_handler);
578 sip->tx_handler = 0;
579 return;
583 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
585 if (written < 0 && errno == EAGAIN)
586 written = 0;
587 else if (written <= 0) {
588 /*TODO: do we really want to disconnect on a failure to write?*/
589 purple_connection_error(gc, _("Could not write"));
590 return;
593 purple_circ_buffer_mark_read(sip->txbuf, written);
596 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
598 static void send_later_cb(gpointer data, gint source, const gchar *error)
600 PurpleConnection *gc = data;
601 struct sipe_account_data *sip;
602 struct sip_connection *conn;
604 if (!PURPLE_CONNECTION_IS_VALID(gc))
606 if (source >= 0)
607 close(source);
608 return;
611 if (source < 0) {
612 purple_connection_error(gc, _("Could not connect"));
613 return;
616 sip = gc->proto_data;
617 sip->fd = source;
618 sip->connecting = FALSE;
619 sip->last_keepalive = time(NULL);
621 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
623 /* If there is more to write now, we need to register a handler */
624 if (sip->txbuf->bufused > 0)
625 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
627 conn = connection_create(sip, source);
628 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
631 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
633 struct sipe_account_data *sip;
634 struct sip_connection *conn;
636 if (!PURPLE_CONNECTION_IS_VALID(gc))
638 if (gsc) purple_ssl_close(gsc);
639 return NULL;
642 sip = gc->proto_data;
643 sip->fd = gsc->fd;
644 sip->gsc = gsc;
645 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
646 sip->connecting = FALSE;
647 sip->last_keepalive = time(NULL);
649 conn = connection_create(sip, gsc->fd);
651 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
653 return sip;
656 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
658 PurpleConnection *gc = data;
659 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
660 if (sip == NULL) return;
662 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
664 /* If there is more to write now */
665 if (sip->txbuf->bufused > 0) {
666 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
671 static void sendlater(PurpleConnection *gc, const char *buf)
673 struct sipe_account_data *sip = gc->proto_data;
675 if (!sip->connecting) {
676 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
677 if (sip->transport == SIPE_TRANSPORT_TLS){
678 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
679 } else {
680 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
681 purple_connection_error(gc, _("Couldn't create socket"));
684 sip->connecting = TRUE;
687 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
688 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
690 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
693 static void sendout_pkt(PurpleConnection *gc, const char *buf)
695 struct sipe_account_data *sip = gc->proto_data;
696 time_t currtime = time(NULL);
697 int writelen = strlen(buf);
699 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
700 if (sip->transport == SIPE_TRANSPORT_UDP) {
701 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
702 purple_debug_info("sipe", "could not send packet\n");
704 } else {
705 int ret;
706 if (sip->fd < 0) {
707 sendlater(gc, buf);
708 return;
711 if (sip->tx_handler) {
712 ret = -1;
713 errno = EAGAIN;
714 } else{
715 if (sip->gsc){
716 ret = purple_ssl_write(sip->gsc, buf, writelen);
717 }else{
718 ret = write(sip->fd, buf, writelen);
722 if (ret < 0 && errno == EAGAIN)
723 ret = 0;
724 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
725 sendlater(gc, buf);
726 return;
729 if (ret < writelen) {
730 if (!sip->tx_handler){
731 if (sip->gsc){
732 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
734 else{
735 sip->tx_handler = purple_input_add(sip->fd,
736 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
737 gc);
741 /* XXX: is it OK to do this? You might get part of a request sent
742 with part of another. */
743 if (sip->txbuf->bufused > 0)
744 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
746 purple_circ_buffer_append(sip->txbuf, buf + ret,
747 writelen - ret);
752 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
754 sendout_pkt(gc, buf);
755 return len;
758 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
760 GSList *tmp = msg->headers;
761 gchar *name;
762 gchar *value;
763 GString *outstr = g_string_new("");
764 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
765 while (tmp) {
766 name = ((struct siphdrelement*) (tmp->data))->name;
767 value = ((struct siphdrelement*) (tmp->data))->value;
768 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
769 tmp = g_slist_next(tmp);
771 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
772 sendout_pkt(sip->gc, outstr->str);
773 g_string_free(outstr, TRUE);
776 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
778 gchar * buf;
779 if (sip->registrar.ntlm_key) {
780 struct sipmsg_breakdown msgbd;
781 gchar *signature_input_str;
782 msgbd.msg = msg;
783 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
784 msgbd.rand = g_strdup_printf("%08x", g_random_int());
785 sip->registrar.ntlm_num++;
786 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
787 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
788 if (signature_input_str != NULL) {
789 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
790 msg->rand = g_strdup(msgbd.rand);
791 msg->num = g_strdup(msgbd.num);
792 g_free(signature_input_str);
794 sipmsg_breakdown_free(&msgbd);
797 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
798 buf = auth_header(sip, &sip->registrar, msg);
799 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
800 sipmsg_add_header(msg, "Authorization", buf);
801 } else {
802 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
804 g_free(buf);
805 } 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")) {
806 sip->registrar.nc = 3;
807 sip->registrar.type = AUTH_TYPE_NTLM;
809 buf = auth_header(sip, &sip->registrar, msg);
810 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
811 g_free(buf);
812 } else {
813 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
817 static char *get_contact(struct sipe_account_data *sip)
819 return g_strdup(sip->contact);
823 * unused. Needed?
824 static char *get_contact_service(struct sipe_account_data *sip)
826 return g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, generateUUIDfromEPID(get_epid()));
827 //return g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
831 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
832 const char *text, const char *body)
834 gchar *name;
835 gchar *value;
836 GString *outstr = g_string_new("");
837 struct sipe_account_data *sip = gc->proto_data;
838 gchar *contact;
839 GSList *tmp;
841 sipmsg_remove_header(msg, "ms-user-data");
843 contact = get_contact(sip);
844 sipmsg_remove_header(msg, "Contact");
845 sipmsg_add_header(msg, "Contact", contact);
846 g_free(contact);
848 /* When sending the acknowlegements and errors, the content length from the original
849 message is still here, but there is no body; we need to make sure we're sending the
850 correct content length */
851 sipmsg_remove_header(msg, "Content-Length");
852 if (body) {
853 gchar len[12];
854 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
855 sipmsg_add_header(msg, "Content-Length", len);
856 } else {
857 sipmsg_remove_header(msg, "Content-Type");
858 sipmsg_add_header(msg, "Content-Length", "0");
861 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
862 //gchar * mic = "MICTODO";
863 msg->response = code;
865 sipmsg_remove_header(msg, "Authentication-Info");
866 sign_outgoing_message(msg, sip, msg->method);
868 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
869 tmp = msg->headers;
870 while (tmp) {
871 name = ((struct siphdrelement*) (tmp->data))->name;
872 value = ((struct siphdrelement*) (tmp->data))->value;
874 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
875 tmp = g_slist_next(tmp);
877 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
878 sendout_pkt(gc, outstr->str);
879 g_string_free(outstr, TRUE);
882 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
884 if (trans->msg) sipmsg_free(trans->msg);
885 sip->transactions = g_slist_remove(sip->transactions, trans);
886 g_free(trans);
889 static struct transaction *
890 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
892 struct transaction *trans = g_new0(struct transaction, 1);
893 trans->time = time(NULL);
894 trans->msg = (struct sipmsg *)msg;
895 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
896 trans->callback = callback;
897 sip->transactions = g_slist_append(sip->transactions, trans);
898 return trans;
901 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
903 struct transaction *trans;
904 GSList *transactions = sip->transactions;
905 gchar *cseq = sipmsg_find_header(msg, "CSeq");
907 while (transactions) {
908 trans = transactions->data;
909 if (!strcmp(trans->cseq, cseq)) {
910 return trans;
912 transactions = transactions->next;
915 return NULL;
918 static struct transaction *
919 send_sip_request(PurpleConnection *gc, const gchar *method,
920 const gchar *url, const gchar *to, const gchar *addheaders,
921 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
923 struct sipe_account_data *sip = gc->proto_data;
924 const char *addh = "";
925 char *buf;
926 struct sipmsg *msg;
927 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
928 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
929 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
930 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
931 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
932 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
933 gchar *route = strdup("");
934 gchar *epid = get_epid(sip); // TODO generate one per account/login
935 struct transaction *trans;
937 if (dialog && dialog->routes)
939 GSList *iter = dialog->routes;
941 while(iter)
943 char *tmp = route;
944 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
945 g_free(tmp);
946 iter = g_slist_next(iter);
950 if (!ourtag && !dialog) {
951 ourtag = gentag();
954 if (!strcmp(method, "REGISTER")) {
955 if (sip->regcallid) {
956 g_free(callid);
957 callid = g_strdup(sip->regcallid);
958 } else {
959 sip->regcallid = g_strdup(callid);
963 if (addheaders) addh = addheaders;
965 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
966 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
967 "From: <sip:%s>%s%s;epid=%s\r\n"
968 "To: <%s>%s%s%s%s\r\n"
969 "Max-Forwards: 70\r\n"
970 "CSeq: %d %s\r\n"
971 "User-Agent: %s\r\n"
972 "Call-ID: %s\r\n"
973 "%s%s"
974 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
975 method,
976 dialog && dialog->request ? dialog->request : url,
977 TRANSPORT_DESCRIPTOR,
978 purple_network_get_my_ip(-1),
979 sip->listenport,
980 branch ? ";branch=" : "",
981 branch ? branch : "",
982 sip->username,
983 ourtag ? ";tag=" : "",
984 ourtag ? ourtag : "",
985 epid,
987 theirtag ? ";tag=" : "",
988 theirtag ? theirtag : "",
989 theirepid ? ";epid=" : "",
990 theirepid ? theirepid : "",
991 dialog ? ++dialog->cseq : ++sip->cseq,
992 method,
993 useragent,
994 callid,
995 route,
996 addh,
997 body ? strlen(body) : 0,
998 body ? body : "");
1001 //printf ("parsing msg buf:\n%s\n\n", buf);
1002 msg = sipmsg_parse_msg(buf);
1004 g_free(buf);
1005 g_free(ourtag);
1006 g_free(theirtag);
1007 g_free(theirepid);
1008 g_free(branch);
1009 g_free(callid);
1010 g_free(route);
1011 g_free(epid);
1013 sign_outgoing_message (msg, sip, method);
1015 buf = sipmsg_to_string (msg);
1017 /* add to ongoing transactions */
1018 trans = transactions_add_buf(sip, msg, tc);
1019 sendout_pkt(gc, buf);
1020 g_free(buf);
1022 return trans;
1025 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1027 gchar *from = g_strdup_printf("sip:%s", sip->username);
1028 gchar *contact = get_contact(sip);
1029 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1030 "Content-Type: application/SOAP+xml\r\n",contact);
1032 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1033 tr->payload = payload;
1035 g_free(from);
1036 g_free(contact);
1037 g_free(hdr);
1040 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1042 send_soap_request_with_cb(sip, body, NULL, NULL);
1045 static char *get_contact_register(struct sipe_account_data *sip)
1047 char *epid = get_epid(sip);
1048 char *uuid = generateUUIDfromEPID(epid);
1049 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1050 g_free(uuid);
1051 g_free(epid);
1052 return(buf);
1055 static void do_register_exp(struct sipe_account_data *sip, int expire)
1057 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1058 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1059 char *to = g_strdup_printf("sip:%s", sip->username);
1060 char *contact = get_contact_register(sip);
1061 char *hdr = g_strdup_printf("Contact: %s\r\n"
1062 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1063 "Event: registration\r\n"
1064 "Allow-Events: presence\r\n"
1065 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1066 "%s", contact, expires);
1067 g_free(contact);
1068 g_free(expires);
1070 sip->registerstatus = 1;
1072 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1073 process_register_response);
1075 g_free(hdr);
1076 g_free(uri);
1077 g_free(to);
1080 static void do_register_cb(struct sipe_account_data *sip)
1082 do_register_exp(sip, -1);
1083 sip->reregister_set = FALSE;
1086 static void do_register(struct sipe_account_data *sip)
1088 do_register_exp(sip, -1);
1092 * Returns URI from provided To or From header.
1094 * Needs to g_free() after use.
1096 * @return URI with sip: prefix
1098 static gchar *parse_from(const gchar *hdr)
1100 gchar *from;
1101 const gchar *tmp, *tmp2 = hdr;
1103 if (!hdr) return NULL;
1104 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1105 tmp = strchr(hdr, '<');
1107 /* i hate the different SIP UA behaviours... */
1108 if (tmp) { /* sip address in <...> */
1109 tmp2 = tmp + 1;
1110 tmp = strchr(tmp2, '>');
1111 if (tmp) {
1112 from = g_strndup(tmp2, tmp - tmp2);
1113 } else {
1114 purple_debug_info("sipe", "found < without > in From\n");
1115 return NULL;
1117 } else {
1118 tmp = strchr(tmp2, ';');
1119 if (tmp) {
1120 from = g_strndup(tmp2, tmp - tmp2);
1121 } else {
1122 from = g_strdup(tmp2);
1125 purple_debug_info("sipe", "got %s\n", from);
1126 return from;
1129 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1131 va_list args;
1132 xmlnode * node = NULL;
1133 const gchar * name;
1135 va_start(args, parent);
1136 while ((name = va_arg(args, const char *)) != NULL) {
1137 node = xmlnode_get_child(parent, name);
1138 if (node == NULL) return NULL;
1139 parent = node;
1141 va_end(args);
1143 return node;
1147 static void
1148 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1150 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1151 send_soap_request(sip, body);
1152 g_free(body);
1155 static void
1156 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1158 if (allow) {
1159 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1160 } else {
1161 purple_debug_info("sipe", "Blocking contact %s\n", who);
1164 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1167 static
1168 void sipe_auth_user_cb(void * data)
1170 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1171 if (!job) return;
1173 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1174 g_free(job);
1177 static
1178 void sipe_deny_user_cb(void * data)
1180 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1181 if (!job) return;
1183 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1184 g_free(job);
1187 static void
1188 sipe_add_permit(PurpleConnection *gc, const char *name)
1190 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1191 sipe_contact_allow_deny(sip, name, TRUE);
1194 static void
1195 sipe_add_deny(PurpleConnection *gc, const char *name)
1197 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1198 sipe_contact_allow_deny(sip, name, FALSE);
1201 /*static void
1202 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1204 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1205 sipe_contact_set_acl(sip, name, "");
1208 static void
1209 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1211 xmlnode *watchers;
1212 xmlnode *watcher;
1213 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1214 if (msg->response != 0 && msg->response != 200) return;
1216 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1218 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1219 if (!watchers) return;
1221 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1222 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1223 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1224 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1226 // TODO pull out optional displayName to pass as alias
1227 if (remote_user) {
1228 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1229 job->who = remote_user;
1230 job->sip = sip;
1231 purple_account_request_authorization(
1232 sip->account,
1233 remote_user,
1234 NULL, // id
1235 alias,
1236 NULL, // message
1237 on_list,
1238 sipe_auth_user_cb,
1239 sipe_deny_user_cb,
1240 (void *) job);
1245 xmlnode_free(watchers);
1246 return;
1249 static void
1250 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1252 PurpleGroup * purple_group = purple_find_group(group->name);
1253 if (!purple_group) {
1254 purple_group = purple_group_new(group->name);
1255 purple_blist_add_group(purple_group, NULL);
1258 if (purple_group) {
1259 group->purple_group = purple_group;
1260 sip->groups = g_slist_append(sip->groups, group);
1261 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1262 } else {
1263 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1267 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1269 struct sipe_group *group;
1270 GSList *entry;
1271 if (sip == NULL) {
1272 return NULL;
1275 entry = sip->groups;
1276 while (entry) {
1277 group = entry->data;
1278 if (group->id == id) {
1279 return group;
1281 entry = entry->next;
1283 return NULL;
1286 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1288 struct sipe_group *group;
1289 GSList *entry;
1290 if (sip == NULL) {
1291 return NULL;
1294 entry = sip->groups;
1295 while (entry) {
1296 group = entry->data;
1297 if (!strcmp(group->name, name)) {
1298 return group;
1300 entry = entry->next;
1302 return NULL;
1305 static void
1306 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1308 gchar *body;
1309 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1310 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1311 send_soap_request(sip, body);
1312 g_free(body);
1313 g_free(group->name);
1314 group->name = g_strdup(name);
1318 * Only appends if no such value already stored.
1319 * Like Set in Java.
1321 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1322 GSList * res = list;
1323 if (!g_slist_find_custom(list, data, func)) {
1324 res = g_slist_insert_sorted(list, data, func);
1326 return res;
1329 static int
1330 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1331 return group1->id - group2->id;
1335 * Returns string like "2 4 7 8" - group ids buddy belong to.
1337 static gchar *
1338 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1339 int i = 0;
1340 gchar *res;
1341 //creating array from GList, converting int to gchar*
1342 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1343 GSList *entry = buddy->groups;
1344 while (entry) {
1345 struct sipe_group * group = entry->data;
1346 ids_arr[i] = g_strdup_printf("%d", group->id);
1347 entry = entry->next;
1348 i++;
1350 ids_arr[i] = NULL;
1351 res = g_strjoinv(" ", ids_arr);
1352 g_strfreev(ids_arr);
1353 return res;
1357 * Sends buddy update to server
1359 static void
1360 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1362 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1363 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1365 if (buddy && purple_buddy) {
1366 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1367 gchar *body;
1368 gchar *groups = sipe_get_buddy_groups_string(buddy);
1369 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1371 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1372 alias, groups, "true", buddy->name, sip->contacts_delta++
1374 send_soap_request(sip, body);
1375 g_free(groups);
1376 g_free(body);
1380 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1382 if (msg->response == 200) {
1383 struct sipe_group *group;
1384 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1385 xmlnode *xml;
1386 xmlnode *node;
1387 char *group_id;
1388 struct sipe_buddy *buddy;
1390 xml = xmlnode_from_str(msg->body, msg->bodylen);
1391 if (!xml) {
1392 g_free(ctx);
1393 return FALSE;
1396 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1397 if (!node) {
1398 g_free(ctx);
1399 xmlnode_free(xml);
1400 return FALSE;
1403 group_id = xmlnode_get_data(node);
1404 if (!group_id) {
1405 g_free(ctx);
1406 xmlnode_free(xml);
1407 return FALSE;
1410 group = g_new0(struct sipe_group, 1);
1411 group->id = (int)g_ascii_strtod(group_id, NULL);
1412 g_free(group_id);
1413 group->name = ctx->group_name;
1415 sipe_group_add(sip, group);
1417 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1418 if (buddy) {
1419 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1422 sipe_group_set_user(sip, ctx->user_name);
1424 g_free(ctx);
1425 xmlnode_free(xml);
1426 return TRUE;
1428 return FALSE;
1431 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1433 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1434 gchar *body;
1435 ctx->group_name = g_strdup(name);
1436 ctx->user_name = g_strdup(who);
1438 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1439 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1440 g_free(body);
1444 * A timer callback
1445 * Should return FALSE if repetitive action is not needed
1447 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1449 gboolean ret;
1450 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1451 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1452 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1453 (sched_action->action)(sched_action->sip, sched_action->payload);
1454 ret = sched_action->repetitive;
1455 g_free(sched_action->payload);
1456 g_free(sched_action->name);
1457 g_free(sched_action);
1458 return ret;
1462 * Do schedule action for execution in the future.
1463 * Non repetitive execution.
1465 * @param name of action (will be copied)
1466 * @param timeout in seconds
1467 * @action callback function
1468 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1470 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1472 struct scheduled_action *sched_action;
1474 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1475 sched_action = g_new0(struct scheduled_action, 1);
1476 sched_action->repetitive = FALSE;
1477 sched_action->name = g_strdup(name);
1478 sched_action->action = action;
1479 sched_action->sip = sip;
1480 sched_action->payload = payload;
1481 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1482 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1483 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1487 * Kills action timer effectively cancelling
1488 * scheduled action
1490 * @param name of action
1492 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1494 GSList *entry;
1496 if (!sip->timeouts || !name) return;
1498 entry = sip->timeouts;
1499 while (entry) {
1500 struct scheduled_action *sched_action = entry->data;
1501 if(!strcmp(sched_action->name, name)) {
1502 GSList *to_delete = entry;
1503 entry = entry->next;
1504 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1505 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1506 purple_timeout_remove(sched_action->timeout_handler);
1507 g_free(sched_action->payload);
1508 g_free(sched_action->name);
1509 g_free(sched_action);
1510 } else {
1511 entry = entry->next;
1516 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1518 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1520 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1522 process_incoming_notify(sip, msg, FALSE, FALSE);
1524 return TRUE;
1527 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1529 gchar *tmp = *resources_uri;
1530 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1531 g_free(tmp);
1535 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1536 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1537 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1538 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1539 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1542 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip){
1543 gchar *to = g_strdup_printf("sip:%s", sip->username);
1544 gchar *contact = get_contact(sip);
1545 gchar *request;
1546 gchar *content;
1547 gchar *resources_uri = g_strdup("");
1548 gchar *require = "";
1549 gchar *accept = "";
1550 gchar *content_type;
1552 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1554 if (sip->msrtc_event_categories) {
1555 require = ", categoryList";
1556 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1557 content_type = "application/msrtc-adrl-categorylist+xml";
1558 content = g_strdup_printf(
1559 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1560 "<action name=\"subscribe\" id=\"63792024\">\n"
1561 "<adhocList>\n%s</adhocList>\n"
1562 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1563 "<category name=\"note\"/>\n"
1564 "<category name=\"state\"/>\n"
1565 "</categoryList>\n"
1566 "</action>\n"
1567 "</batchSub>", sip->username, resources_uri);
1568 } else {
1569 content_type = "application/adrl+xml";
1570 content = g_strdup_printf(
1571 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1572 "<create xmlns=\"\">\n%s</create>\n"
1573 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1575 g_free(resources_uri);
1577 request = g_strdup_printf(
1578 "Require: adhoclist%s\r\n"
1579 "Supported: eventlist\r\n"
1580 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1581 "Supported: ms-piggyback-first-notify\r\n"
1582 "Supported: com.microsoft.autoextend\r\n"
1583 "Supported: ms-benotify\r\n"
1584 "Proxy-Require: ms-benotify\r\n"
1585 "Event: presence\r\n"
1586 "Content-Type: %s\r\n"
1587 "Contact: %s\r\n", require, accept, content_type, contact);
1588 g_free(contact);
1590 /* subscribe to buddy presence */
1591 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1592 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1594 g_free(content);
1595 g_free(to);
1596 g_free(request);
1600 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1601 * The user sends a single SUBSCRIBE request to the subscribed contact.
1602 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1606 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1608 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1609 gchar *tmp = get_contact(sip);
1610 gchar *request;
1611 gchar *content;
1612 request = g_strdup_printf(
1613 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1614 "Supported: ms-piggyback-first-notify\r\n"
1615 "Supported: com.microsoft.autoextend\r\n"
1616 "Supported: ms-benotify\r\n"
1617 "Proxy-Require: ms-benotify\r\n"
1618 "Event: presence\r\n"
1619 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1620 "Contact: %s\r\n", tmp);
1622 content = g_strdup_printf(
1623 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1624 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1625 "<resource uri=\"%s\"/>\n"
1626 "</adhocList>\n"
1627 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1628 "<category name=\"note\"/>\n"
1629 "<category name=\"state\"/>\n"
1630 "</categoryList>\n"
1631 "</action>\n"
1632 "</batchSub>", sip->username, to
1635 g_free(tmp);
1637 /* subscribe to buddy presence */
1638 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1640 g_free(content);
1641 g_free(to);
1642 g_free(request);
1645 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1647 if (!purple_status_is_active(status))
1648 return;
1650 if (account->gc) {
1651 struct sipe_account_data *sip = account->gc->proto_data;
1653 if (sip) {
1654 g_free(sip->status);
1655 sip->status = g_strdup(purple_status_get_id(status));
1656 send_presence_status(sip);
1661 static void
1662 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1664 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1665 sipe_group_set_user(sip, name);
1668 static void
1669 sipe_group_buddy(PurpleConnection *gc,
1670 const char *who,
1671 const char *old_group_name,
1672 const char *new_group_name)
1674 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1675 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1676 struct sipe_group * old_group = NULL;
1677 struct sipe_group * new_group;
1679 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1680 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1682 if(!buddy) { // buddy not in roaming list
1683 return;
1686 if (old_group_name) {
1687 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1689 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1691 if (old_group) {
1692 buddy->groups = g_slist_remove(buddy->groups, old_group);
1693 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1696 if (!new_group) {
1697 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1698 } else {
1699 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1700 sipe_group_set_user(sip, who);
1704 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1706 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1707 struct sipe_buddy *b;
1709 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1711 // Prepend sip: if needed
1712 if (strncmp("sip:", buddy->name, 4)) {
1713 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1714 purple_blist_rename_buddy(buddy, buf);
1715 g_free(buf);
1718 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1719 b = g_new0(struct sipe_buddy, 1);
1720 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1721 b->name = g_strdup(buddy->name);
1722 g_hash_table_insert(sip->buddies, b->name, b);
1723 sipe_group_buddy(gc, b->name, NULL, group->name);
1724 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1725 } else {
1726 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1730 static void sipe_free_buddy(struct sipe_buddy *buddy)
1732 g_free(buddy->name);
1733 g_free(buddy->annotation);
1734 g_free(buddy->device_name);
1735 g_slist_free(buddy->groups);
1736 g_free(buddy);
1740 * Unassociates buddy from group first.
1741 * Then see if no groups left, removes buddy completely.
1742 * Otherwise updates buddy groups on server.
1744 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1746 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1747 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1748 struct sipe_group *g = NULL;
1750 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1752 if (!b) return;
1754 if (group) {
1755 g = sipe_group_find_by_name(sip, group->name);
1758 if (g) {
1759 b->groups = g_slist_remove(b->groups, g);
1760 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1763 if (g_slist_length(b->groups) < 1) {
1764 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1765 sipe_cancel_scheduled_action(sip, action_name);
1766 g_free(action_name);
1768 g_hash_table_remove(sip->buddies, buddy->name);
1770 if (b->name) {
1771 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1772 send_soap_request(sip, body);
1773 g_free(body);
1776 sipe_free_buddy(b);
1777 } else {
1778 //updates groups on server
1779 sipe_group_set_user(sip, b->name);
1784 static void
1785 sipe_rename_group(PurpleConnection *gc,
1786 const char *old_name,
1787 PurpleGroup *group,
1788 GList *moved_buddies)
1790 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1791 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1792 if (group) {
1793 sipe_group_rename(sip, s_group, group->name);
1794 } else {
1795 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1799 static void
1800 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1802 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1803 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1804 if (s_group) {
1805 gchar *body;
1806 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1807 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1808 send_soap_request(sip, body);
1809 g_free(body);
1811 sip->groups = g_slist_remove(sip->groups, s_group);
1812 g_free(s_group->name);
1813 g_free(s_group);
1814 } else {
1815 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1819 static GList *sipe_status_types(PurpleAccount *acc)
1821 PurpleStatusType *type;
1822 GList *types = NULL;
1824 // Online
1825 type = purple_status_type_new_with_attrs(
1826 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1827 // Translators: noun
1828 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1829 NULL);
1830 types = g_list_append(types, type);
1832 // Busy
1833 type = purple_status_type_new_with_attrs(
1834 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1835 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1836 NULL);
1837 types = g_list_append(types, type);
1839 // Do Not Disturb (not user settable)
1840 type = purple_status_type_new_with_attrs(
1841 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1842 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1843 NULL);
1844 types = g_list_append(types, type);
1846 // Be Right Back
1847 type = purple_status_type_new_with_attrs(
1848 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1849 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1850 NULL);
1851 types = g_list_append(types, type);
1853 // Away
1854 type = purple_status_type_new_with_attrs(
1855 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1856 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1857 NULL);
1858 types = g_list_append(types, type);
1860 //On The Phone
1861 type = purple_status_type_new_with_attrs(
1862 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1863 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1864 NULL);
1865 types = g_list_append(types, type);
1867 //Out To Lunch
1868 type = purple_status_type_new_with_attrs(
1869 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1870 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1871 NULL);
1872 types = g_list_append(types, type);
1874 //Appear Offline
1875 type = purple_status_type_new_full(
1876 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1877 types = g_list_append(types, type);
1879 // Offline
1880 type = purple_status_type_new_full(
1881 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1882 types = g_list_append(types, type);
1884 return types;
1888 * A callback for g_hash_table_foreach
1890 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1892 sipe_subscribe_presence_single(sip, buddy->name);
1896 * Removes entries from purple buddy list
1897 * that does not correspond ones in the roaming contact list.
1899 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1900 GSList *buddies = purple_find_buddies(sip->account, NULL);
1901 GSList *entry = buddies;
1902 struct sipe_buddy *buddy;
1903 PurpleBuddy *b;
1904 PurpleGroup *g;
1906 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1907 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1908 while (entry) {
1909 b = entry->data;
1910 g = purple_buddy_get_group(b);
1911 buddy = g_hash_table_lookup(sip->buddies, b->name);
1912 if(buddy) {
1913 gboolean in_sipe_groups = FALSE;
1914 GSList *entry2 = buddy->groups;
1915 while (entry2) {
1916 struct sipe_group *group = entry2->data;
1917 if (!strcmp(group->name, g->name)) {
1918 in_sipe_groups = TRUE;
1919 break;
1921 entry2 = entry2->next;
1923 if(!in_sipe_groups) {
1924 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1925 purple_blist_remove_buddy(b);
1927 } else {
1928 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1929 purple_blist_remove_buddy(b);
1931 entry = entry->next;
1935 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1937 int len = msg->bodylen;
1939 gchar *tmp = sipmsg_find_header(msg, "Event");
1940 xmlnode *item;
1941 xmlnode *isc;
1942 const gchar *contacts_delta;
1943 xmlnode *group_node;
1944 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1945 return FALSE;
1948 /* Convert the contact from XML to Purple Buddies */
1949 isc = xmlnode_from_str(msg->body, len);
1950 if (!isc) {
1951 return FALSE;
1954 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1955 if (contacts_delta) {
1956 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1959 /* Parse groups */
1960 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1961 struct sipe_group * group = g_new0(struct sipe_group, 1);
1962 const char *name = xmlnode_get_attrib(group_node, "name");
1964 if (!strncmp(name, "~", 1)) {
1965 // TODO translate
1966 name = "Other Contacts";
1968 group->name = g_strdup(name);
1969 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1971 sipe_group_add(sip, group);
1974 // Make sure we have at least one group
1975 if (g_slist_length(sip->groups) == 0) {
1976 struct sipe_group * group = g_new0(struct sipe_group, 1);
1977 PurpleGroup *purple_group;
1978 // TODO translate
1979 group->name = g_strdup("Other Contacts");
1980 group->id = 1;
1981 purple_group = purple_group_new(group->name);
1982 purple_blist_add_group(purple_group, NULL);
1983 sip->groups = g_slist_append(sip->groups, group);
1986 /* Parse contacts */
1987 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1988 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1989 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1990 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1991 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1992 gchar **item_groups;
1993 struct sipe_group *group = NULL;
1994 struct sipe_buddy *buddy = NULL;
1995 int i = 0;
1997 // assign to group Other Contacts if nothing else received
1998 if(!groups || !strcmp("", groups) ) {
1999 group = sipe_group_find_by_name(sip, "Other Contacts");
2000 groups = group ? g_strdup_printf("%d", group->id) : "1";
2003 item_groups = g_strsplit(groups, " ", 0);
2005 while (item_groups[i]) {
2006 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2008 // If couldn't find the right group for this contact, just put them in the first group we have
2009 if (group == NULL && g_slist_length(sip->groups) > 0) {
2010 group = sip->groups->data;
2013 if (group != NULL) {
2014 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2015 if (!b){
2016 b = purple_buddy_new(sip->account, buddy_name, uri);
2017 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2020 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2021 if (name != NULL && strlen(name) != 0) {
2022 purple_blist_alias_buddy(b, name);
2026 if (!buddy) {
2027 buddy = g_new0(struct sipe_buddy, 1);
2028 buddy->name = g_strdup(b->name);
2029 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2032 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2034 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2035 } else {
2036 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2037 name);
2040 i++;
2041 } // while, contact groups
2042 g_strfreev(item_groups);
2043 g_free(groups);
2044 g_free(name);
2045 g_free(buddy_name);
2046 g_free(uri);
2048 } // for, contacts
2050 xmlnode_free(isc);
2052 sipe_cleanup_local_blist(sip);
2054 //subscribe to buddies
2055 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2056 //if(sip->msrtc_event_categories){
2057 sipe_subscribe_presence_batched(sip);
2058 //}else{
2059 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2061 sip->subscribed_buddies = TRUE;
2064 return 0;
2068 * Subscribe roaming contacts
2070 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2072 gchar *to = g_strdup_printf("sip:%s", sip->username);
2073 gchar *tmp = get_contact(sip);
2074 gchar *hdr = g_strdup_printf(
2075 "Event: vnd-microsoft-roaming-contacts\r\n"
2076 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2077 "Supported: com.microsoft.autoextend\r\n"
2078 "Supported: ms-benotify\r\n"
2079 "Proxy-Require: ms-benotify\r\n"
2080 "Supported: ms-piggyback-first-notify\r\n"
2081 "Contact: %s\r\n", tmp);
2082 g_free(tmp);
2084 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2085 g_free(to);
2086 g_free(hdr);
2089 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2091 gchar *to = g_strdup_printf("sip:%s", sip->username);
2092 gchar *tmp = get_contact(sip);
2093 gchar *hdr = g_strdup_printf(
2094 "Event: presence.wpending\r\n"
2095 "Accept: text/xml+msrtc.wpending\r\n"
2096 "Supported: com.microsoft.autoextend\r\n"
2097 "Supported: ms-benotify\r\n"
2098 "Proxy-Require: ms-benotify\r\n"
2099 "Supported: ms-piggyback-first-notify\r\n"
2100 "Contact: %s\r\n", tmp);
2101 g_free(tmp);
2103 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2104 g_free(to);
2105 g_free(hdr);
2109 * Fires on deregistration event initiated by server.
2110 * [MS-SIPREGE] SIP extension.
2113 // 2007 Example
2115 // Content-Type: text/registration-event
2116 // subscription-state: terminated;expires=0
2117 // ms-diagnostics-public: 4141;reason="User disabled"
2119 // deregistered;event=rejected
2121 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2123 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2124 gchar *event = NULL;
2125 gchar *reason = NULL;
2126 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2128 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2129 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2131 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2132 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2133 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2134 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2135 } else {
2136 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2137 return;
2140 if (warning != NULL) {
2141 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2142 } else { // for LCS2005
2143 int error_id = 0;
2144 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2145 error_id = 4140; // [MS-SIPREGE]
2146 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2147 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2148 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2149 error_id = 4141;
2150 reason = g_strdup(_("User disabled")); // [MS-OCER]
2151 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2152 error_id = 4142;
2153 reason = g_strdup(_("User moved")); // [MS-OCER]
2156 g_free(event);
2157 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2158 g_free(reason);
2160 sip->gc->wants_to_die = TRUE;
2161 purple_connection_error(sip->gc, warning);
2162 g_free(warning);
2166 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2168 const gchar *contacts_delta;
2169 xmlnode *xml;
2171 xml = xmlnode_from_str(msg->body, msg->bodylen);
2172 if (!xml)
2174 return;
2177 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2178 if (contacts_delta)
2180 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2183 xmlnode_free(xml);
2187 * When we receive some self (BE) NOTIFY with a new subscriber
2188 * we sends a setSubscribers request to him [SIP-PRES]
2192 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2194 gchar *contact;
2195 gchar *to;
2196 xmlnode *xml;
2197 xmlnode *node;
2199 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2201 xml = xmlnode_from_str(msg->body, msg->bodylen);
2202 if (!xml) return;
2204 contact = get_contact(sip);
2205 to = g_strdup_printf("sip:%s", sip->username);
2207 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2208 const char *user;
2209 gchar *hdr;
2210 gchar *body;
2212 user = xmlnode_get_attrib(node, "user");
2213 if (!user) continue;
2215 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2217 hdr = g_strdup_printf(
2218 "Contact: %s\r\n"
2219 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2221 body = g_strdup_printf(
2222 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2223 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2224 "</setSubscribers>", user);
2226 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2227 g_free(body);
2228 g_free(hdr);
2231 g_free(to);
2232 g_free(contact);
2233 xmlnode_free(xml);
2236 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2238 gchar *to = g_strdup_printf("sip:%s", sip->username);
2239 gchar *tmp = get_contact(sip);
2240 gchar *hdr = g_strdup_printf(
2241 "Event: vnd-microsoft-roaming-ACL\r\n"
2242 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2243 "Supported: com.microsoft.autoextend\r\n"
2244 "Supported: ms-benotify\r\n"
2245 "Proxy-Require: ms-benotify\r\n"
2246 "Supported: ms-piggyback-first-notify\r\n"
2247 "Contact: %s\r\n", tmp);
2248 g_free(tmp);
2250 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2251 g_free(to);
2252 g_free(hdr);
2256 * To request for presence information about the user, access level settings that have already been configured by the user
2257 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2258 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2261 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2263 gchar *to = g_strdup_printf("sip:%s", sip->username);
2264 gchar *tmp = get_contact(sip);
2265 gchar *hdr = g_strdup_printf(
2266 "Event: vnd-microsoft-roaming-self\r\n"
2267 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2268 "Supported: com.microsoft.autoextend\r\n"
2269 "Supported: ms-benotify\r\n"
2270 "Proxy-Require: ms-benotify\r\n"
2271 "Supported: ms-piggyback-first-notify\r\n"
2272 "Contact: %s\r\n"
2273 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2275 gchar *body=g_strdup(
2276 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2277 "<roaming type=\"categories\"/>"
2278 "<roaming type=\"containers\"/>"
2279 "<roaming type=\"subscribers\"/></roamingList>");
2281 g_free(tmp);
2282 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2283 g_free(body);
2284 g_free(to);
2285 g_free(hdr);
2288 /** Subscription for provisioning information to help with initial
2289 * configuration. This subscription is a one-time query (denoted by the Expires header,
2290 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2291 * configuration, meeting policies, and policy settings that Communicator must enforce.
2292 * TODO: for what we need this information.
2295 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2297 gchar *to = g_strdup_printf("sip:%s", sip->username);
2298 gchar *tmp = get_contact(sip);
2299 gchar *hdr = g_strdup_printf(
2300 "Event: vnd-microsoft-provisioning-v2\r\n"
2301 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2302 "Supported: com.microsoft.autoextend\r\n"
2303 "Supported: ms-benotify\r\n"
2304 "Proxy-Require: ms-benotify\r\n"
2305 "Supported: ms-piggyback-first-notify\r\n"
2306 "Expires: 0\r\n"
2307 "Contact: %s\r\n"
2308 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2309 gchar *body = g_strdup(
2310 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2311 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2312 "<provisioningGroup name=\"ucPolicy\"/>"
2313 "</provisioningGroupList>");
2315 g_free(tmp);
2316 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2317 g_free(body);
2318 g_free(to);
2319 g_free(hdr);
2322 /* IM Session (INVITE and MESSAGE methods) */
2324 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2326 struct sip_im_session *session;
2327 GSList *entry;
2328 if (sip == NULL || who == NULL) {
2329 return NULL;
2332 entry = sip->im_sessions;
2333 while (entry) {
2334 session = entry->data;
2335 if ((who != NULL && !strcmp(who, session->with))) {
2336 return session;
2338 entry = entry->next;
2340 return NULL;
2343 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2345 struct sip_im_session *session = find_im_session(sip, who);
2346 if (!session) {
2347 session = g_new0(struct sip_im_session, 1);
2348 session->with = g_strdup(who);
2349 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2350 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2352 return session;
2355 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2357 struct sip_dialog *dialog = session->dialog;
2358 GSList *entry;
2360 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2362 if (dialog) {
2363 entry = dialog->routes;
2364 while (entry) {
2365 g_free(entry->data);
2366 entry = g_slist_remove(entry, entry->data);
2368 entry = dialog->supported;
2369 while (entry) {
2370 g_free(entry->data);
2371 entry = g_slist_remove(entry, entry->data);
2373 g_free(dialog->callid);
2374 g_free(dialog->ourtag);
2375 g_free(dialog->theirtag);
2376 g_free(dialog->theirepid);
2377 g_free(dialog->request);
2379 g_free(session->dialog);
2381 entry = session->outgoing_message_queue;
2382 while (entry) {
2383 g_free(entry->data);
2384 entry = g_slist_remove(entry, entry->data);
2387 g_hash_table_destroy(session->unconfirmed_messages);
2389 g_free(session->with);
2390 g_free(session);
2393 static gboolean
2394 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2396 gboolean ret = TRUE;
2398 if (msg->response != 200) {
2399 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2400 return FALSE;
2403 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2405 return ret;
2409 * Asks UA/proxy about its capabilities.
2411 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2413 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2414 gchar *contact = get_contact(sip);
2415 gchar *request;
2416 request = g_strdup_printf(
2417 "Accept: application/sdp\r\n"
2418 "Contact: %s\r\n", contact);
2420 g_free(contact);
2422 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2424 g_free(to);
2425 g_free(request);
2428 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2430 char *msg, *msg_tmp;
2431 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2432 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2433 g_free(msg_tmp);
2434 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2435 "possibly because one or more persons are offline:\n%s") ,
2436 msg ? msg : "");
2437 purple_conv_present_error(with, sip->account, msg_tmp);
2438 g_free(msg);
2439 g_free(msg_tmp);
2442 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2444 static gboolean
2445 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2447 gboolean ret = TRUE;
2448 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2449 struct sip_im_session * session = find_im_session(sip, with);
2450 struct sip_dialog *dialog;
2451 gchar *cseq;
2452 char *key;
2453 gchar *message;
2455 if (!session) {
2456 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2457 g_free(with);
2458 return FALSE;
2461 dialog = session->dialog;
2462 if (!dialog) {
2463 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2464 g_free(with);
2465 return FALSE;
2468 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2469 key = g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq));
2470 g_free(cseq);
2471 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2473 if (msg->response != 200) {
2474 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2476 sipe_present_message_undelivered_err(with, sip, message);
2477 im_session_destroy(sip, session);
2478 ret = FALSE;
2479 } else {
2480 g_hash_table_remove(session->unconfirmed_messages, key);
2481 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2482 key, g_hash_table_size(session->unconfirmed_messages));
2485 g_free(key);
2486 g_free(with);
2488 sipe_im_process_queue(sip, session);
2489 return ret;
2492 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2494 gchar *hdr;
2495 gchar *fullto;
2496 gchar *tmp;
2497 char *msgformat;
2498 char *msgtext;
2499 gchar *msgr_value;
2500 gchar *msgr;
2502 if (strncmp("sip:", session->with, 4)) {
2503 fullto = g_strdup_printf("sip:%s", session->with);
2504 } else {
2505 fullto = g_strdup(session->with);
2508 sipe_parse_html(msg, &msgformat, &msgtext);
2509 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2511 msgr_value = sipmsg_get_msgr_string(msgformat);
2512 g_free(msgformat);
2513 if (msgr_value) {
2514 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2515 g_free(msgr_value);
2516 } else {
2517 msgr = g_strdup("");
2520 tmp = get_contact(sip);
2521 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2522 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2523 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2524 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2525 tmp, msgr);
2526 g_free(tmp);
2527 g_free(msgr);
2529 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2530 g_free(msgtext);
2531 g_free(hdr);
2532 g_free(fullto);
2536 static void
2537 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2539 GSList *entry = session->outgoing_message_queue;
2541 if (session->outgoing_invite) return; //do not send messages until INVITE responded.
2543 while (entry) {
2544 char *key = g_strdup_printf("<%s><%d><MESSAGE>", session->dialog->callid, (session->dialog->cseq) + 1);
2545 char *queued_msg = entry->data;
2546 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2547 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2548 key, g_hash_table_size(session->unconfirmed_messages));
2549 g_free(key);
2550 sipe_send_message(sip, session, queued_msg);
2551 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2552 g_free(queued_msg);
2556 static void
2557 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2559 GSList *hdr = msg->headers;
2560 struct siphdrelement *elem;
2561 gchar *contact;
2563 while(hdr)
2565 elem = hdr->data;
2566 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2568 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2569 dialog->routes = g_slist_append(dialog->routes, route);
2571 hdr = g_slist_next(hdr);
2574 if (outgoing)
2576 dialog->routes = g_slist_reverse(dialog->routes);
2579 if (dialog->routes)
2581 dialog->request = dialog->routes->data;
2582 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2585 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2586 dialog->routes = g_slist_append(dialog->routes, contact);
2589 static void
2590 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2592 GSList *hdr = msg->headers;
2593 struct siphdrelement *elem;
2594 while(hdr)
2596 elem = hdr->data;
2597 if(!g_ascii_strcasecmp(elem->name, "Supported")
2598 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2600 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2603 hdr = g_slist_next(hdr);
2607 static void
2608 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2610 gchar *us = outgoing ? "From" : "To";
2611 gchar *them = outgoing ? "To" : "From";
2613 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2614 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2615 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2616 if (!dialog->theirepid) {
2617 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2619 if (!dialog->theirepid) {
2620 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2623 sipe_get_route_header(msg, dialog, outgoing);
2624 sipe_get_supported_header(msg, dialog, outgoing);
2628 static gboolean
2629 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2631 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2632 struct sip_im_session * session = find_im_session(sip, with);
2633 struct sip_dialog *dialog;
2634 char *cseq;
2635 char *key;
2636 gchar *message;
2638 if (!session) {
2639 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2640 g_free(with);
2641 return FALSE;
2644 dialog = session->dialog;
2645 if (!dialog) {
2646 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2647 g_free(with);
2648 return FALSE;
2651 sipe_parse_dialog(msg, dialog, TRUE);
2653 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2654 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2655 g_free(cseq);
2656 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2658 if (msg->response != 200) {
2659 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2661 sipe_present_message_undelivered_err(with, sip, message);
2662 im_session_destroy(sip, session);
2663 g_free(with);
2664 return FALSE;
2667 dialog->cseq = 0;
2668 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2669 session->outgoing_invite = NULL;
2670 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2671 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2672 if (session->outgoing_message_queue) {
2673 char *queued_msg = session->outgoing_message_queue->data;
2674 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2675 g_free(queued_msg);
2679 sipe_im_process_queue(sip, session);
2681 g_hash_table_remove(session->unconfirmed_messages, key);
2682 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2683 key, g_hash_table_size(session->unconfirmed_messages));
2685 g_free(key);
2686 g_free(with);
2687 return TRUE;
2691 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session *session, const gchar *msg_body)
2693 gchar *hdr;
2694 gchar *to;
2695 gchar *contact;
2696 gchar *body;
2697 char *msgformat;
2698 char *msgtext;
2699 char *base64_msg;
2700 char *ms_text_format;
2701 gchar *msgr_value;
2702 gchar *msgr;
2703 char *key;
2705 if (session->dialog) {
2706 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2707 return;
2710 session->dialog = g_new0(struct sip_dialog, 1);
2711 session->dialog->callid = gencallid();
2713 if (strstr(session->with, "sip:")) {
2714 to = g_strdup(session->with);
2715 } else {
2716 to = g_strdup_printf("sip:%s", session->with);
2719 sipe_parse_html(msg_body, &msgformat, &msgtext);
2720 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2722 msgr_value = sipmsg_get_msgr_string(msgformat);
2723 g_free(msgformat);
2724 msgr = "";
2725 if (msgr_value) {
2726 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2727 g_free(msgr_value);
2730 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2731 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2732 g_free(msgtext);
2733 g_free(msgr);
2734 g_free(base64_msg);
2736 key = g_strdup_printf("<%s><%d><INVITE>", session->dialog->callid, (session->dialog->cseq) + 1);
2737 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2738 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2739 key, g_hash_table_size(session->unconfirmed_messages));
2740 g_free(key);
2742 contact = get_contact(sip);
2743 hdr = g_strdup_printf(
2744 "Contact: %s\r\n%s"
2745 "Content-Type: application/sdp\r\n",
2746 contact, ms_text_format);
2747 g_free(ms_text_format);
2749 body = g_strdup_printf(
2750 "v=0\r\n"
2751 "o=- 0 0 IN IP4 %s\r\n"
2752 "s=session\r\n"
2753 "c=IN IP4 %s\r\n"
2754 "t=0 0\r\n"
2755 "m=message %d sip null\r\n"
2756 "a=accept-types:text/plain text/html image/gif "
2757 "multipart/alternative application/im-iscomposing+xml\r\n",
2758 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2760 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2761 to, to, hdr, body, session->dialog, process_invite_response);
2763 g_free(to);
2764 g_free(body);
2765 g_free(hdr);
2766 g_free(contact);
2769 static void
2770 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2772 if (session) {
2773 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2774 im_session_destroy(sip, session);
2778 static void
2779 sipe_convo_closed(PurpleConnection * gc, const char *who)
2781 struct sipe_account_data *sip = gc->proto_data;
2783 purple_debug_info("sipe", "conversation with %s closed\n", who);
2784 im_session_close(sip, find_im_session(sip, who));
2787 static void
2788 im_session_close_all (struct sipe_account_data *sip)
2790 GSList *entry = sip->im_sessions;
2791 while (entry) {
2792 im_session_close (sip, entry->data);
2793 entry = sip->im_sessions;
2797 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2799 struct sipe_account_data *sip;
2800 gchar *to;
2801 struct sip_im_session *session;
2803 sip = gc->proto_data;
2804 to = g_strdup(who);
2806 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2808 session = find_or_create_im_session(sip, who);
2810 // Queue the message
2811 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
2813 if (session->dialog && session->dialog->callid) {
2814 sipe_im_process_queue(sip, session);
2815 } else if (!session->outgoing_invite) {
2816 // Need to send the INVITE to get the outgoing dialog setup
2817 sipe_invite(sip, session, what);
2820 g_free(to);
2821 return 1;
2824 /* End IM Session (INVITE and MESSAGE methods) */
2826 static unsigned int
2827 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2829 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2830 struct sip_im_session *session;
2832 if (state == PURPLE_NOT_TYPING)
2833 return 0;
2835 session = find_im_session(sip, who);
2837 if (session && session->dialog) {
2838 send_sip_request(gc, "INFO", who, who,
2839 "Content-Type: application/xml\r\n",
2840 SIPE_SEND_TYPING, session->dialog, NULL);
2842 return SIPE_TYPING_SEND_TIMEOUT;
2845 static gboolean resend_timeout(struct sipe_account_data *sip)
2847 GSList *tmp = sip->transactions;
2848 time_t currtime = time(NULL);
2849 while (tmp) {
2850 struct transaction *trans = tmp->data;
2851 tmp = tmp->next;
2852 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2853 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2854 /* TODO 408 */
2855 } else {
2856 if ((currtime - trans->time > 2) && trans->retries == 0) {
2857 trans->retries++;
2858 sendout_sipmsg(sip, trans->msg);
2862 return TRUE;
2865 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2867 /* register again when security token expires */
2868 /* we have to start a new authentication as the security token
2869 * is almost expired by sending a not signed REGISTER message */
2870 purple_debug_info("sipe", "do a full reauthentication\n");
2871 sipe_auth_free(&sip->registrar);
2872 sip->registerstatus = 0;
2873 do_register(sip);
2874 sip->reauthenticate_set = FALSE;
2877 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2879 gchar *from;
2880 gchar *contenttype;
2881 gboolean found = FALSE;
2883 from = parse_from(sipmsg_find_header(msg, "From"));
2885 if (!from) return;
2887 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2889 contenttype = sipmsg_find_header(msg, "Content-Type");
2890 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2892 gchar *html = get_html_message(contenttype, msg->body);
2893 serv_got_im(sip->gc, g_strdup(from), g_strdup(html), 0, time(NULL));
2894 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2895 found = TRUE;
2897 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2898 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2899 xmlnode *state;
2900 gchar *statedata;
2902 if (!isc) {
2903 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2904 return;
2907 state = xmlnode_get_child(isc, "state");
2909 if (!state) {
2910 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2911 xmlnode_free(isc);
2912 return;
2915 statedata = xmlnode_get_data(state);
2916 if (statedata) {
2917 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2918 else serv_got_typing_stopped(sip->gc, from);
2920 g_free(statedata);
2922 xmlnode_free(isc);
2923 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2924 found = TRUE;
2926 if (!found) {
2927 purple_debug_info("sipe", "got unknown mime-type");
2928 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2930 g_free(from);
2933 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2935 gchar *ms_text_format;
2936 gchar *from;
2937 gchar *body;
2938 struct sip_im_session *session;
2940 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
2942 // Only accept text invitations
2943 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2944 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2945 return;
2948 from = parse_from(sipmsg_find_header(msg, "From"));
2949 session = find_or_create_im_session (sip, from);
2950 if (session) {
2951 if (session->dialog) {
2952 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2953 } else {
2954 session->dialog = g_new0(struct sip_dialog, 1);
2956 sipe_parse_dialog(msg, session->dialog, FALSE);
2958 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2959 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2960 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2961 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2963 } else {
2964 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2967 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2968 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2969 if (ms_text_format) {
2970 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
2972 gchar *html = get_html_message(ms_text_format, NULL);
2973 if (html) {
2974 serv_got_im(sip->gc, g_strdup(from), g_strdup(html), 0, time(NULL));
2975 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2979 g_free(from);
2981 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2982 sipmsg_remove_header(msg, "Ms-Text-Format");
2983 sipmsg_remove_header(msg, "EndPoints");
2984 sipmsg_remove_header(msg, "User-Agent");
2985 sipmsg_remove_header(msg, "Roster-Manager");
2987 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2988 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2990 body = g_strdup_printf(
2991 "v=0\r\n"
2992 "o=- 0 0 IN IP4 %s\r\n"
2993 "s=session\r\n"
2994 "c=IN IP4 %s\r\n"
2995 "t=0 0\r\n"
2996 "m=message %d sip sip:%s\r\n"
2997 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2998 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2999 sip->realport, sip->username);
3000 send_sip_response(sip->gc, msg, 200, "OK", body);
3001 g_free(body);
3004 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3006 gchar *body;
3008 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3009 sipmsg_remove_header(msg, "EndPoints");
3010 sipmsg_remove_header(msg, "User-Agent");
3012 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3013 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3015 body = g_strdup_printf(
3016 "v=0\r\n"
3017 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3018 "s=session\r\n"
3019 "c=IN IP4 0.0.0.0\r\n"
3020 "t=0 0\r\n"
3021 "m=message %d sip sip:%s\r\n"
3022 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3023 sip->realport, sip->username);
3024 send_sip_response(sip->gc, msg, 200, "OK", body);
3025 g_free(body);
3028 static void sipe_connection_cleanup(struct sipe_account_data *);
3029 static void create_connection(struct sipe_account_data *, gchar *, int);
3031 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3033 gchar *tmp;
3034 //gchar krb5_token;
3035 const gchar *expires_header;
3036 int expires;
3037 GSList *hdr = msg->headers;
3038 struct siphdrelement *elem;
3040 expires_header = sipmsg_find_header(msg, "Expires");
3041 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3042 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3044 switch (msg->response) {
3045 case 200:
3046 if (expires == 0) {
3047 sip->registerstatus = 0;
3048 } else {
3049 int i = 0;
3050 gchar *contact_hdr = NULL;
3051 gchar *gruu = NULL;
3052 gchar *epid;
3053 gchar *uuid;
3055 if (!sip->reregister_set) {
3056 gchar *action_name = g_strdup_printf("<%s>", "registration");
3057 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
3058 g_free(action_name);
3059 sip->reregister_set = TRUE;
3062 sip->registerstatus = 3;
3064 if (!sip->reauthenticate_set) {
3065 /* we have to reauthenticate as our security token expires
3066 after eight hours (be five minutes early) */
3067 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3068 guint reauth_timeout = (8 * 3600) - 360;
3069 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3070 g_free(action_name);
3071 sip->reauthenticate_set = TRUE;
3074 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3076 epid = get_epid(sip);
3077 uuid = generateUUIDfromEPID(epid);
3078 g_free(epid);
3080 // There can be multiple Contact headers (one per location where the user is logged in) so
3081 // make sure to only get the one for this uuid
3082 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3083 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3084 if (valid_contact) {
3085 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3086 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3087 g_free(valid_contact);
3088 break;
3089 } else {
3090 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3093 g_free(uuid);
3095 g_free(sip->contact);
3096 if(gruu) {
3097 sip->contact = g_strdup_printf("<%s>", gruu);
3098 g_free(gruu);
3099 } else {
3100 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3101 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);
3103 sip->msrtc_event_categories = FALSE;
3105 while(hdr)
3107 elem = hdr->data;
3108 if(!g_ascii_strcasecmp(elem->name, "Supported"))
3110 if (strstr(elem->value, "msrtc-event-categories")){
3111 sip->msrtc_event_categories = TRUE;
3113 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s, %d\n", elem->value, sip->msrtc_event_categories);
3115 hdr = g_slist_next(hdr);
3118 if (!sip->subscribed) { //do it just once, not every re-register
3119 tmp = sipmsg_find_header(msg, "Allow-Events");
3120 sipe_options_request(sip, sip->sipdomain);
3121 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
3122 sipe_subscribe_roaming_contacts(sip, msg);
3124 sipe_subscribe_roaming_acl(sip, msg);
3125 sipe_subscribe_roaming_self(sip, msg);
3126 sipe_subscribe_roaming_provisioning(sip, msg);
3127 sipe_subscribe_presence_wpending(sip, msg);
3128 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3129 sip->subscribed = TRUE;
3132 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
3133 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
3134 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
3135 } else {
3136 tmp = sipmsg_find_header(msg, "ms-keep-alive");
3137 if (tmp) {
3138 sipe_keep_alive_timeout(sip, tmp);
3142 // Should we remove the transaction here?
3143 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3144 transactions_remove(sip, tc);
3146 break;
3147 case 301:
3149 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3151 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3152 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3153 gchar **tmp;
3154 gchar *hostname;
3155 int port = 0;
3156 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3157 int i = 1;
3159 tmp = g_strsplit(parts[0], ":", 0);
3160 hostname = g_strdup(tmp[0]);
3161 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3162 g_strfreev(tmp);
3164 while (parts[i]) {
3165 tmp = g_strsplit(parts[i], "=", 0);
3166 if (tmp[1]) {
3167 if (g_strcasecmp("transport", tmp[0]) == 0) {
3168 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3169 transport = SIPE_TRANSPORT_TCP;
3170 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3171 transport = SIPE_TRANSPORT_UDP;
3175 g_strfreev(tmp);
3176 i++;
3178 g_strfreev(parts);
3180 /* Close old connection */
3181 sipe_connection_cleanup(sip);
3183 /* Create new connection */
3184 sip->transport = transport;
3185 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3186 hostname, port, TRANSPORT_DESCRIPTOR);
3187 create_connection(sip, hostname, port);
3189 g_free(redirect);
3191 break;
3192 case 401:
3193 if (sip->registerstatus != 2) {
3194 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3195 if (sip->registrar.retries > 3) {
3196 sip->gc->wants_to_die = TRUE;
3197 purple_connection_error(sip->gc, _("Wrong Password"));
3198 return TRUE;
3200 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3201 tmp = sipmsg_find_auth_header(msg, "NTLM");
3202 } else {
3203 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3205 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3206 fill_auth(sip, tmp, &sip->registrar);
3207 sip->registerstatus = 2;
3208 if (sip->account->disconnecting) {
3209 do_register_exp(sip, 0);
3210 } else {
3211 do_register(sip);
3214 break;
3215 case 403:
3217 gchar *warning = sipmsg_find_header(msg, "Warning");
3218 if (warning != NULL) {
3219 /* Example header:
3220 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3222 gchar **tmp = g_strsplit(warning, "\"", 0);
3223 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3224 g_strfreev(tmp);
3225 } else {
3226 warning = g_strdup(_("You have been rejected by the server"));
3229 sip->gc->wants_to_die = TRUE;
3230 purple_connection_error(sip->gc, warning);
3231 g_free(warning);
3232 return TRUE;
3234 break;
3235 case 404:
3237 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3238 if (warning != NULL) {
3239 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3240 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3241 g_free(reason);
3242 } else {
3243 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3246 sip->gc->wants_to_die = TRUE;
3247 purple_connection_error(sip->gc, warning);
3248 g_free(warning);
3249 return TRUE;
3251 break;
3252 case 503:
3254 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3255 if (warning != NULL) {
3256 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3257 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3258 g_free(reason);
3259 } else {
3260 warning = g_strdup(_("Service unavailable: no reason given"));
3263 sip->gc->wants_to_die = TRUE;
3264 purple_connection_error(sip->gc, warning);
3265 g_free(warning);
3266 return TRUE;
3268 break;
3270 return TRUE;
3273 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3275 const char *uri;
3276 xmlnode *xn_categories;
3277 xmlnode *xn_category;
3278 xmlnode *xn_node;
3279 const char *activity = NULL;
3281 xn_categories = xmlnode_from_str(data, len);
3282 uri = xmlnode_get_attrib(xn_categories, "uri");
3284 for (xn_category = xmlnode_get_child(xn_categories, "category");
3285 xn_category ;
3286 xn_category = xmlnode_get_next_twin(xn_category) )
3288 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3290 if (!strcmp(attrVar, "note"))
3292 char *note;
3293 struct sipe_buddy *sbuddy;
3294 xn_node = xmlnode_get_child(xn_category, "note");
3295 if (!xn_node) continue;
3296 xn_node = xmlnode_get_child(xn_node, "body");
3297 if (!xn_node) continue;
3299 note = xmlnode_get_data(xn_node);
3301 if(uri){
3302 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3304 if (sbuddy && note)
3306 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3307 sbuddy->annotation = g_strdup(note);
3310 g_free(note);
3312 else if(!strcmp(attrVar, "state"))
3314 char *data;
3315 int avail;
3316 xn_node = xmlnode_get_child(xn_category, "state");
3317 if (!xn_node) continue;
3318 xn_node = xmlnode_get_child(xn_node, "availability");
3319 if (!xn_node) continue;
3321 data = xmlnode_get_data(xn_node);
3322 avail = atoi(data);
3323 g_free(data);
3325 if (avail < 3000)
3326 activity = SIPE_STATUS_ID_UNKNOWN;
3327 else if (avail < 4500)
3328 activity = SIPE_STATUS_ID_AVAILABLE;
3329 else if (avail < 6000)
3330 activity = SIPE_STATUS_ID_BRB;
3331 else if (avail < 7500)
3332 activity = SIPE_STATUS_ID_ONPHONE;
3333 else if (avail < 9000)
3334 activity = SIPE_STATUS_ID_BUSY;
3335 else if (avail < 12000)
3336 activity = SIPE_STATUS_ID_DND;
3337 else if (avail < 18000)
3338 activity = SIPE_STATUS_ID_AWAY;
3339 else
3340 activity = SIPE_STATUS_ID_OFFLINE;
3343 if(activity) {
3344 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
3345 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3348 xmlnode_free(xn_categories);
3351 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3353 const char *uri,*state;
3354 xmlnode *xn_list;
3355 xmlnode *xn_resource;
3356 xmlnode *xn_instance;
3358 xn_list = xmlnode_from_str(data, len);
3360 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3361 xn_resource;
3362 xn_resource = xmlnode_get_next_twin(xn_resource) )
3364 xn_instance = xmlnode_get_child(xn_resource, "instance");
3365 if (!xn_instance) return;
3367 state = xmlnode_get_attrib(xn_instance, "state");
3368 uri = xmlnode_get_attrib(xn_instance, "cid");
3369 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3370 if(strstr(state,"resubscribe")){
3371 sipe_subscribe_presence_single(sip, uri);
3376 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3378 const gchar *uri;
3379 gchar *getbasic;
3380 gchar *activity = NULL;
3381 xmlnode *pidf;
3382 xmlnode *basicstatus = NULL, *tuple, *status;
3383 gboolean isonline = FALSE;
3384 xmlnode *display_name_node;
3386 pidf = xmlnode_from_str(data, len);
3387 if (!pidf) {
3388 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3389 return;
3392 uri = xmlnode_get_attrib(pidf, "entity");
3394 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3396 if ((status = xmlnode_get_child(tuple, "status"))) {
3397 basicstatus = xmlnode_get_child(status, "basic");
3401 if (!basicstatus) {
3402 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3403 xmlnode_free(pidf);
3404 return;
3407 getbasic = xmlnode_get_data(basicstatus);
3408 if (!getbasic) {
3409 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3410 xmlnode_free(pidf);
3411 return;
3414 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3415 if (strstr(getbasic, "open")) {
3416 isonline = TRUE;
3418 g_free(getbasic);
3420 display_name_node = xmlnode_get_child(pidf, "display-name");
3421 // updating display name if alias was just URI
3422 if (display_name_node) {
3423 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3424 GSList *entry = buddies;
3425 PurpleBuddy *p_buddy;
3426 char * display_name = xmlnode_get_data(display_name_node);
3428 while (entry) {
3429 const char *server_alias;
3430 char *alias;
3432 p_buddy = entry->data;
3434 alias = (char *)purple_buddy_get_alias(p_buddy);
3435 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3436 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3437 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3438 purple_blist_alias_buddy(p_buddy, display_name);
3440 g_free(alias);
3442 server_alias = purple_buddy_get_server_alias(p_buddy);
3443 if (display_name &&
3444 ( (server_alias && strcmp(display_name, server_alias))
3445 || !server_alias || strlen(server_alias) == 0 )
3447 purple_blist_server_alias_buddy(p_buddy, display_name);
3450 entry = entry->next;
3452 g_free(display_name);
3455 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3456 if ((status = xmlnode_get_child(tuple, "status"))) {
3457 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3458 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3459 activity = xmlnode_get_data(basicstatus);
3460 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3466 if (isonline) {
3467 const gchar * status_id = NULL;
3468 if (activity) {
3469 if (strstr(activity, "busy")) {
3470 status_id = SIPE_STATUS_ID_BUSY;
3471 } else if (strstr(activity, "away")) {
3472 status_id = SIPE_STATUS_ID_AWAY;
3476 if (!status_id) {
3477 status_id = SIPE_STATUS_ID_AVAILABLE;
3480 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3481 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3482 } else {
3483 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
3486 g_free(activity);
3487 xmlnode_free(pidf);
3490 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3492 const char *availability;
3493 const char *activity;
3494 const char *display_name = NULL;
3495 const char *activity_name;
3496 const char *name;
3497 char *uri;
3498 int avl;
3499 int act;
3500 struct sipe_buddy *sbuddy;
3502 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3504 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3505 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3506 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3507 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3508 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3509 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3510 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3511 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3512 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3513 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3514 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3515 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3517 name = xmlnode_get_attrib(xn_presentity, "uri");
3518 uri = g_strdup_printf("sip:%s", name);
3519 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3520 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3522 // updating display name if alias was just URI
3523 if (xn_display_name) {
3524 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3525 GSList *entry = buddies;
3526 PurpleBuddy *p_buddy;
3527 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3529 while (entry) {
3530 const char *email_str, *server_alias;
3532 p_buddy = entry->data;
3534 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3535 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3536 purple_blist_alias_buddy(p_buddy, display_name);
3539 server_alias = purple_buddy_get_server_alias(p_buddy);
3540 if (display_name &&
3541 ( (server_alias && strcmp(display_name, server_alias))
3542 || !server_alias || strlen(server_alias) == 0 )
3544 purple_blist_server_alias_buddy(p_buddy, display_name);
3547 if (email) {
3548 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3549 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3550 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3554 entry = entry->next;
3558 avl = atoi(availability);
3559 act = atoi(activity);
3561 if (act <= 100)
3562 activity_name = SIPE_STATUS_ID_AWAY;
3563 else if (act <= 150)
3564 activity_name = SIPE_STATUS_ID_LUNCH;
3565 else if (act <= 300)
3566 activity_name = SIPE_STATUS_ID_BRB;
3567 else if (act <= 400)
3568 activity_name = SIPE_STATUS_ID_AVAILABLE;
3569 else if (act <= 500)
3570 activity_name = SIPE_STATUS_ID_ONPHONE;
3571 else if (act <= 600)
3572 activity_name = SIPE_STATUS_ID_BUSY;
3573 else
3574 activity_name = SIPE_STATUS_ID_AVAILABLE;
3576 if (avl == 0)
3577 activity_name = SIPE_STATUS_ID_OFFLINE;
3579 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3580 if (sbuddy)
3582 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3583 sbuddy->annotation = NULL;
3584 if (note) { sbuddy->annotation = g_strdup(note); }
3586 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3587 sbuddy->device_name = NULL;
3588 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3591 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3592 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3593 g_free(note);
3594 xmlnode_free(xn_presentity);
3595 g_free(uri);
3598 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3600 char *ctype = sipmsg_find_header(msg, "Content-Type");
3602 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3604 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3605 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3607 const char *content = msg->body;
3608 unsigned length = msg->bodylen;
3609 PurpleMimeDocument *mime = NULL;
3611 if (strstr(ctype, "multipart"))
3613 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3614 const char *content_type;
3615 GList* parts;
3616 mime = purple_mime_document_parse(doc);
3617 parts = purple_mime_document_get_parts(mime);
3618 while(parts) {
3619 content = purple_mime_part_get_data(parts->data);
3620 length = purple_mime_part_get_length(parts->data);
3621 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3622 if(content_type && strstr(content_type,"application/rlmi+xml"))
3624 process_incoming_notify_rlmi_resub(sip, content, length);
3626 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3628 process_incoming_notify_msrtc(sip, content, length);
3630 else
3632 process_incoming_notify_rlmi(sip, content, length);
3634 parts = parts->next;
3636 g_free(doc);
3638 if (mime)
3640 purple_mime_document_free(mime);
3643 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3645 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3647 else if(strstr(ctype, "application/rlmi+xml"))
3649 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3652 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3654 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3656 else
3658 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3663 * Dispatcher for all incoming subscription information
3664 * whether it comes from NOTIFY, BENOTIFY requests or
3665 * piggy-backed to subscription's OK responce.
3667 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3668 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3670 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3672 gchar *event = sipmsg_find_header(msg, "Event");
3673 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3674 const char *uri,*state;
3675 xmlnode *xn_list;
3676 xmlnode *xn_resource;
3677 xmlnode *xn_instance;
3679 int expires = 0;
3681 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3682 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3684 if (!request)
3686 const gchar *expires_header;
3687 expires_header = sipmsg_find_header(msg, "Expires");
3688 expires = expires_header ? strtol(expires_header, NULL, 10) : 0;
3689 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires);
3692 if (!subscription_state || strstr(subscription_state, "active"))
3694 if (event && !g_ascii_strcasecmp(event, "presence"))
3696 sipe_process_presence(sip, msg);
3698 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3700 sipe_process_roaming_contacts(sip, msg, NULL);
3702 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") && benotify)
3704 sipe_process_roaming_self(sip, msg);
3706 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
3708 //@TODO
3709 purple_debug_info("sipe", "vnd-microsoft-provisioning-v2 data is not supported yet.");
3711 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3713 sipe_process_roaming_acl(sip, msg);
3715 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3717 sipe_process_presence_wpending(sip, msg);
3719 else
3721 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3725 //The server sends a (BE)NOTIFY with the status 'terminated'
3726 if(request && subscription_state && strstr(subscription_state, "terminated") )
3728 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3729 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3730 g_free(from);
3733 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
3735 sipe_process_registration_notify(sip, msg);
3738 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3739 if (request && !benotify)
3741 sipmsg_remove_header(msg, "Expires");
3742 sipmsg_remove_header(msg, "subscription-state");
3743 sipmsg_remove_header(msg, "Event");
3744 sipmsg_remove_header(msg, "Require");
3745 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3750 * unused. Needed?
3752 static gchar* gen_xpidf(struct sipe_account_data *sip)
3754 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3755 "<presence>\r\n"
3756 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3757 "<display name=\"sip:%s\"/>\r\n"
3758 "<atom id=\"1234\">\r\n"
3759 "<address uri=\"sip:%s\">\r\n"
3760 "<status status=\"%s\"/>\r\n"
3761 "</address>\r\n"
3762 "</atom>\r\n"
3763 "</presence>\r\n",
3764 sip->username,
3765 sip->username,
3766 sip->username,
3767 sip->status);
3768 return doc;
3773 static gchar* gen_pidf(struct sipe_account_data *sip)
3775 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3776 "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" xmlns:ep=\"urn:ietf:params:xml:ns:pidf:status:rpid-status\" xmlns:ci=\"urn:ietf:params:xml:ns:pidf:cipid\" entity=\"sip:%s\">\r\n"
3777 "<tuple id=\"0\">\r\n"
3778 "<status>\r\n"
3779 "<basic>open</basic>\r\n"
3780 "<ep:activities>\r\n"
3781 " <ep:activity>%s</ep:activity>\r\n"
3782 "</ep:activities>"
3783 "</status>\r\n"
3784 "</tuple>\r\n"
3785 "<ci:display-name>%s</ci:display-name>\r\n"
3786 "</presence>",
3787 sip->username,
3788 sip->status,
3789 sip->username);
3790 return doc;
3794 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
3796 int availability = 300; // online
3797 int activity = 400; // Available
3798 gchar *name;
3799 gchar *body;
3800 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
3801 activity = 100;
3802 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
3803 activity = 150;
3804 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
3805 activity = 300;
3806 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
3807 activity = 400;
3808 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
3809 activity = 500;
3810 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
3811 activity = 600;
3812 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
3813 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
3814 availability = 0; // offline
3815 activity = 100;
3816 } else {
3817 activity = 400; // available
3820 name = g_strdup_printf("sip: sip:%s", sip->username);
3821 //@TODO: send user data - state; add hostname in upper case
3822 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3823 send_soap_request_with_cb(sip, body, NULL , NULL);
3824 g_free(name);
3825 g_free(body);
3828 static gboolean
3829 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3831 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3832 if (msg->response == 200) {
3833 sip->status_version = 0;
3834 send_presence_status(sip);
3836 return TRUE;
3839 static gboolean
3840 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3842 if (msg->response == 409) {
3843 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3844 // TODO need to parse the version #'s?
3845 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3846 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3847 gchar *tmp;
3848 gchar *hdr;
3850 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
3852 tmp = get_contact(sip);
3853 hdr = g_strdup_printf("Contact: %s\r\n"
3854 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3856 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3858 g_free(tmp);
3859 g_free(hdr);
3860 g_free(uri);
3861 g_free(doc);
3863 return TRUE;
3866 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
3868 int code;
3869 gchar *uri;
3870 gchar *doc;
3871 gchar *tmp;
3872 gchar *hdr;
3873 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
3874 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
3875 code = 12000;
3876 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
3877 code = 9000;
3878 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
3879 code = 7500;
3880 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
3881 code = 6000;
3882 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
3883 code = 4500;
3884 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
3885 code = 3000;
3886 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
3887 code = 0;
3888 } else {
3889 // Offline or invisible
3890 code = 18000;
3893 uri = g_strdup_printf("sip:%s", sip->username);
3894 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3895 sip->status_version, code,
3896 sip->status_version, code,
3897 sip->status_version, note ? note : "",
3898 sip->status_version, note ? note : "",
3899 sip->status_version, note ? note : ""
3901 sip->status_version++;
3903 tmp = get_contact(sip);
3904 hdr = g_strdup_printf("Contact: %s\r\n"
3905 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3907 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
3909 g_free(tmp);
3910 g_free(hdr);
3911 g_free(uri);
3912 g_free(doc);
3915 static void send_presence_status(struct sipe_account_data *sip)
3917 PurpleStatus * status = purple_account_get_active_status(sip->account);
3918 const gchar *note;
3919 if (!status) return;
3921 note = purple_status_get_attr_string(status, "message");
3923 if(sip->msrtc_event_categories){
3924 send_presence_category_publish(sip, note);
3925 } else {
3926 send_presence_soap(sip, note);
3930 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3932 gboolean found = FALSE;
3933 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3934 if (msg->response == 0) { /* request */
3935 if (!strcmp(msg->method, "MESSAGE")) {
3936 process_incoming_message(sip, msg);
3937 found = TRUE;
3938 } else if (!strcmp(msg->method, "NOTIFY")) {
3939 purple_debug_info("sipe","send->process_incoming_notify\n");
3940 process_incoming_notify(sip, msg, TRUE, FALSE);
3941 found = TRUE;
3942 } else if (!strcmp(msg->method, "BENOTIFY")) {
3943 purple_debug_info("sipe","send->process_incoming_benotify\n");
3944 process_incoming_notify(sip, msg, TRUE, TRUE);
3945 found = TRUE;
3946 } else if (!strcmp(msg->method, "INVITE")) {
3947 process_incoming_invite(sip, msg);
3948 found = TRUE;
3949 } else if (!strcmp(msg->method, "OPTIONS")) {
3950 process_incoming_options(sip, msg);
3951 found = TRUE;
3952 } else if (!strcmp(msg->method, "INFO")) {
3953 // TODO needs work
3954 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3955 if (from) {
3956 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3958 g_free(from);
3959 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3960 found = TRUE;
3961 } else if (!strcmp(msg->method, "ACK")) {
3962 // ACK's don't need any response
3963 found = TRUE;
3964 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3965 // LCS 2005 sends us these - just respond 200 OK
3966 found = TRUE;
3967 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3968 } else if (!strcmp(msg->method, "BYE")) {
3969 struct sip_im_session *session;
3970 gchar *from;
3971 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3973 from = parse_from(sipmsg_find_header(msg, "From"));
3974 session = find_im_session (sip, from);
3975 g_free(from);
3977 if (session) {
3978 // TODO Let the user know the other user left the conversation?
3979 im_session_destroy(sip, session);
3982 found = TRUE;
3983 } else {
3984 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3986 } else { /* response */
3987 struct transaction *trans = transactions_find(sip, msg);
3988 if (trans) {
3989 if (msg->response == 407) {
3990 gchar *resend, *auth, *ptmp;
3992 if (sip->proxy.retries > 30) return;
3993 sip->proxy.retries++;
3994 /* do proxy authentication */
3996 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3998 fill_auth(sip, ptmp, &sip->proxy);
3999 auth = auth_header(sip, &sip->proxy, trans->msg);
4000 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4001 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4002 g_free(auth);
4003 resend = sipmsg_to_string(trans->msg);
4004 /* resend request */
4005 sendout_pkt(sip->gc, resend);
4006 g_free(resend);
4007 } else {
4008 if (msg->response == 100 || msg->response == 180) {
4009 /* ignore provisional response */
4010 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4011 } else {
4012 sip->proxy.retries = 0;
4013 if (!strcmp(trans->msg->method, "REGISTER")) {
4014 if (msg->response == 401)
4016 sip->registrar.retries++;
4017 sip->registrar.expires = 0;
4019 else
4021 sip->registrar.retries = 0;
4023 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4024 } else {
4025 if (msg->response == 401) {
4026 gchar *resend, *auth, *ptmp;
4028 if (sip->registrar.retries > 4) return;
4029 sip->registrar.retries++;
4031 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4032 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4033 } else {
4034 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4037 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4039 fill_auth(sip, ptmp, &sip->registrar);
4040 auth = auth_header(sip, &sip->registrar, trans->msg);
4041 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4042 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
4044 //sipmsg_remove_header(trans->msg, "Authorization");
4045 //sipmsg_add_header(trans->msg, "Authorization", auth);
4046 g_free(auth);
4047 resend = sipmsg_to_string(trans->msg);
4048 /* resend request */
4049 sendout_pkt(sip->gc, resend);
4050 g_free(resend);
4054 if (trans->callback) {
4055 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4056 /* call the callback to process response*/
4057 (trans->callback)(sip, msg, trans);
4059 /* Not sure if this is needed or what needs to be done
4060 but transactions seem to be removed prematurely so
4061 this only removes them if the response is 200 OK */
4062 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4063 /*Has a bug and it's unneccesary*/
4064 /*transactions_remove(sip, trans);*/
4068 found = TRUE;
4069 } else {
4070 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4073 if (!found) {
4074 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4078 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4080 char *cur;
4081 char *dummy;
4082 struct sipmsg *msg;
4083 int restlen;
4084 cur = conn->inbuf;
4086 /* according to the RFC remove CRLF at the beginning */
4087 while (*cur == '\r' || *cur == '\n') {
4088 cur++;
4090 if (cur != conn->inbuf) {
4091 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4092 conn->inbufused = strlen(conn->inbuf);
4095 /* Received a full Header? */
4096 sip->processing_input = TRUE;
4097 while (sip->processing_input &&
4098 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4099 time_t currtime = time(NULL);
4100 cur += 2;
4101 cur[0] = '\0';
4102 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4103 msg = sipmsg_parse_header(conn->inbuf);
4104 cur[0] = '\r';
4105 cur += 2;
4106 restlen = conn->inbufused - (cur - conn->inbuf);
4107 if (restlen >= msg->bodylen) {
4108 dummy = g_malloc(msg->bodylen + 1);
4109 memcpy(dummy, cur, msg->bodylen);
4110 dummy[msg->bodylen] = '\0';
4111 msg->body = dummy;
4112 cur += msg->bodylen;
4113 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4114 conn->inbufused = strlen(conn->inbuf);
4115 } else {
4116 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4117 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4118 sipmsg_free(msg);
4119 return;
4122 /*if (msg->body) {
4123 purple_debug_info("sipe", "body:\n%s", msg->body);
4126 // Verify the signature before processing it
4127 if (sip->registrar.ntlm_key) {
4128 struct sipmsg_breakdown msgbd;
4129 gchar *signature_input_str;
4130 gchar *signature = NULL;
4131 gchar *rspauth;
4132 msgbd.msg = msg;
4133 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4134 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4135 if (signature_input_str != NULL) {
4136 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4138 g_free(signature_input_str);
4140 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4142 if (signature != NULL) {
4143 if (rspauth != NULL) {
4144 if (purple_ntlm_verify_signature (signature, rspauth)) {
4145 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4146 process_input_message(sip, msg);
4147 } else {
4148 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4149 purple_connection_error(sip->gc, _("Invalid message signature received"));
4150 sip->gc->wants_to_die = TRUE;
4152 } else if (msg->response == 401) {
4153 purple_connection_error(sip->gc, _("Wrong Password"));
4154 sip->gc->wants_to_die = TRUE;
4156 g_free(signature);
4159 g_free(rspauth);
4160 sipmsg_breakdown_free(&msgbd);
4161 } else {
4162 process_input_message(sip, msg);
4165 sipmsg_free(msg);
4169 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4171 PurpleConnection *gc = data;
4172 struct sipe_account_data *sip = gc->proto_data;
4173 struct sipmsg *msg;
4174 int len;
4175 time_t currtime;
4177 static char buffer[65536];
4178 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4179 buffer[len] = '\0';
4180 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4181 msg = sipmsg_parse_msg(buffer);
4182 if (msg) process_input_message(sip, msg);
4186 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4188 struct sipe_account_data *sip = gc->proto_data;
4189 PurpleSslConnection *gsc = sip->gsc;
4191 purple_debug_error("sipe", "%s",debug);
4192 purple_connection_error(gc, msg);
4194 /* Invalidate this connection. Next send will open a new one */
4195 if (gsc) {
4196 connection_remove(sip, gsc->fd);
4197 purple_ssl_close(gsc);
4199 sip->gsc = NULL;
4200 sip->fd = -1;
4203 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4205 PurpleConnection *gc = data;
4206 struct sipe_account_data *sip;
4207 struct sip_connection *conn;
4208 int readlen, len;
4209 gboolean firstread = TRUE;
4211 /* NOTE: This check *IS* necessary */
4212 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4213 purple_ssl_close(gsc);
4214 return;
4217 sip = gc->proto_data;
4218 conn = connection_find(sip, gsc->fd);
4219 if (conn == NULL) {
4220 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4221 gc->wants_to_die = TRUE;
4222 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4223 return;
4226 /* Read all available data from the SSL connection */
4227 do {
4228 /* Increase input buffer size as needed */
4229 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4230 conn->inbuflen += SIMPLE_BUF_INC;
4231 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4232 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4235 /* Try to read as much as there is space left in the buffer */
4236 readlen = conn->inbuflen - conn->inbufused - 1;
4237 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4239 if (len < 0 && errno == EAGAIN) {
4240 /* Try again later */
4241 return;
4242 } else if (len < 0) {
4243 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4244 return;
4245 } else if (firstread && (len == 0)) {
4246 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4247 return;
4250 conn->inbufused += len;
4251 firstread = FALSE;
4253 /* Equivalence indicates that there is possibly more data to read */
4254 } while (len == readlen);
4256 conn->inbuf[conn->inbufused] = '\0';
4257 process_input(sip, conn);
4261 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4263 PurpleConnection *gc = data;
4264 struct sipe_account_data *sip = gc->proto_data;
4265 int len;
4266 struct sip_connection *conn = connection_find(sip, source);
4267 if (!conn) {
4268 purple_debug_error("sipe", "Connection not found!\n");
4269 return;
4272 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4273 conn->inbuflen += SIMPLE_BUF_INC;
4274 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4277 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4279 if (len < 0 && errno == EAGAIN)
4280 return;
4281 else if (len <= 0) {
4282 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4283 connection_remove(sip, source);
4284 if (sip->fd == source) sip->fd = -1;
4285 return;
4288 conn->inbufused += len;
4289 conn->inbuf[conn->inbufused] = '\0';
4291 process_input(sip, conn);
4294 /* Callback for new connections on incoming TCP port */
4295 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4297 PurpleConnection *gc = data;
4298 struct sipe_account_data *sip = gc->proto_data;
4299 struct sip_connection *conn;
4301 int newfd = accept(source, NULL, NULL);
4303 conn = connection_create(sip, newfd);
4305 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4308 static void login_cb(gpointer data, gint source, const gchar *error_message)
4310 PurpleConnection *gc = data;
4311 struct sipe_account_data *sip;
4312 struct sip_connection *conn;
4314 if (!PURPLE_CONNECTION_IS_VALID(gc))
4316 if (source >= 0)
4317 close(source);
4318 return;
4321 if (source < 0) {
4322 purple_connection_error(gc, _("Could not connect"));
4323 return;
4326 sip = gc->proto_data;
4327 sip->fd = source;
4328 sip->last_keepalive = time(NULL);
4330 conn = connection_create(sip, source);
4332 do_register(sip);
4334 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4337 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4339 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4340 if (sip == NULL) return;
4342 do_register(sip);
4345 static guint sipe_ht_hash_nick(const char *nick)
4347 char *lc = g_utf8_strdown(nick, -1);
4348 guint bucket = g_str_hash(lc);
4349 g_free(lc);
4351 return bucket;
4354 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4356 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4359 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4361 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4363 sip->listen_data = NULL;
4365 if (listenfd == -1) {
4366 purple_connection_error(sip->gc, _("Could not create listen socket"));
4367 return;
4370 sip->fd = listenfd;
4372 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4373 sip->listenfd = sip->fd;
4375 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4377 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4378 do_register(sip);
4381 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4383 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4384 int addr_size;
4386 sip->query_data = NULL;
4388 if (!hosts || !hosts->data) {
4389 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4390 return;
4393 addr_size = GPOINTER_TO_INT(hosts->data);
4394 hosts = g_slist_remove(hosts, hosts->data);
4395 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4396 g_free(hosts->data);
4397 hosts = g_slist_remove(hosts, hosts->data);
4398 while (hosts) {
4399 hosts = g_slist_remove(hosts, hosts->data);
4400 g_free(hosts->data);
4401 hosts = g_slist_remove(hosts, hosts->data);
4404 /* create socket for incoming connections */
4405 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4406 sipe_udp_host_resolved_listen_cb, sip);
4407 if (sip->listen_data == NULL) {
4408 purple_connection_error(sip->gc, _("Could not create listen socket"));
4409 return;
4413 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4414 gpointer data)
4416 PurpleConnection *gc = data;
4417 struct sipe_account_data *sip;
4419 /* If the connection is already disconnected, we don't need to do anything else */
4420 if (!PURPLE_CONNECTION_IS_VALID(gc))
4421 return;
4423 sip = gc->proto_data;
4424 sip->fd = -1;
4425 sip->gsc = NULL;
4427 switch(error) {
4428 case PURPLE_SSL_CONNECT_FAILED:
4429 purple_connection_error(gc, _("Connection Failed"));
4430 break;
4431 case PURPLE_SSL_HANDSHAKE_FAILED:
4432 purple_connection_error(gc, _("SSL Handshake Failed"));
4433 break;
4434 case PURPLE_SSL_CERTIFICATE_INVALID:
4435 purple_connection_error(gc, _("SSL Certificate Invalid"));
4436 break;
4440 static void
4441 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4443 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4444 PurpleProxyConnectData *connect_data;
4446 sip->listen_data = NULL;
4448 sip->listenfd = listenfd;
4449 if (sip->listenfd == -1) {
4450 purple_connection_error(sip->gc, _("Could not create listen socket"));
4451 return;
4454 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4455 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4456 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4457 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4458 sipe_newconn_cb, sip->gc);
4459 purple_debug_info("sipe", "connecting to %s port %d\n",
4460 sip->realhostname, sip->realport);
4461 /* open tcp connection to the server */
4462 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4463 sip->realport, login_cb, sip->gc);
4465 if (connect_data == NULL) {
4466 purple_connection_error(sip->gc, _("Couldn't create socket"));
4471 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4473 PurpleAccount *account = sip->account;
4474 PurpleConnection *gc = sip->gc;
4476 if (purple_account_get_bool(account, "useport", FALSE)) {
4477 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4478 port = purple_account_get_int(account, "port", 0);
4479 } else {
4480 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4483 sip->realhostname = hostname;
4484 sip->realport = port;
4486 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4487 hostname, port);
4489 /* TODO: is there a good default grow size? */
4490 if (sip->transport != SIPE_TRANSPORT_UDP)
4491 sip->txbuf = purple_circ_buffer_new(0);
4493 if (sip->transport == SIPE_TRANSPORT_TLS) {
4494 /* SSL case */
4495 if (!purple_ssl_is_supported()) {
4496 gc->wants_to_die = TRUE;
4497 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4498 return;
4501 purple_debug_info("sipe", "using SSL\n");
4503 sip->gsc = purple_ssl_connect(account, hostname, port,
4504 login_cb_ssl, sipe_ssl_connect_failure, gc);
4505 if (sip->gsc == NULL) {
4506 purple_connection_error(gc, _("Could not create SSL context"));
4507 return;
4509 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4510 /* UDP case */
4511 purple_debug_info("sipe", "using UDP\n");
4513 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4514 if (sip->query_data == NULL) {
4515 purple_connection_error(gc, _("Could not resolve hostname"));
4517 } else {
4518 /* TCP case */
4519 purple_debug_info("sipe", "using TCP\n");
4520 /* create socket for incoming connections */
4521 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4522 sipe_tcp_connect_listen_cb, sip);
4523 if (sip->listen_data == NULL) {
4524 purple_connection_error(gc, _("Could not create listen socket"));
4525 return;
4530 /* Service list for autodection */
4531 static const struct sipe_service_data service_autodetect[] = {
4532 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4533 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4534 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4535 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4536 { NULL, NULL, 0 }
4539 /* Service list for SSL/TLS */
4540 static const struct sipe_service_data service_tls[] = {
4541 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4542 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4543 { NULL, NULL, 0 }
4546 /* Service list for TCP */
4547 static const struct sipe_service_data service_tcp[] = {
4548 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4549 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4550 { NULL, NULL, 0 }
4553 /* Service list for UDP */
4554 static const struct sipe_service_data service_udp[] = {
4555 { "sip", "udp", SIPE_TRANSPORT_UDP },
4556 { NULL, NULL, 0 }
4559 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4560 static void resolve_next_service(struct sipe_account_data *sip,
4561 const struct sipe_service_data *start)
4563 if (start) {
4564 sip->service_data = start;
4565 } else {
4566 sip->service_data++;
4567 if (sip->service_data->service == NULL) {
4568 gchar *hostname;
4569 /* Try connecting to the SIP hostname directly */
4570 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4571 if (sip->auto_transport) {
4572 // If SSL is supported, default to using it; OCS servers aren't configured
4573 // by default to accept TCP
4574 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4575 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4576 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4579 hostname = g_strdup(sip->sipdomain);
4580 create_connection(sip, hostname, 0);
4581 return;
4585 /* Try to resolve next service */
4586 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4587 sip->service_data->transport,
4588 sip->sipdomain,
4589 srvresolved, sip);
4592 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4594 struct sipe_account_data *sip = data;
4596 sip->srv_query_data = NULL;
4598 /* find the host to connect to */
4599 if (results) {
4600 gchar *hostname = g_strdup(resp->hostname);
4601 int port = resp->port;
4602 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4603 hostname, port);
4604 g_free(resp);
4606 sip->transport = sip->service_data->type;
4608 create_connection(sip, hostname, port);
4609 } else {
4610 resolve_next_service(sip, NULL);
4614 static void sipe_login(PurpleAccount *account)
4616 PurpleConnection *gc;
4617 struct sipe_account_data *sip;
4618 gchar **signinname_login, **userserver, **domain_user;
4619 const char *transport;
4621 const char *username = purple_account_get_username(account);
4622 gc = purple_account_get_connection(account);
4624 if (strpbrk(username, " \t\v\r\n") != NULL) {
4625 gc->wants_to_die = TRUE;
4626 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4627 return;
4630 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4631 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4632 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4633 sip->gc = gc;
4634 sip->account = account;
4635 sip->reregister_set = FALSE;
4636 sip->reauthenticate_set = FALSE;
4637 sip->subscribed = FALSE;
4638 sip->subscribed_buddies = FALSE;
4640 signinname_login = g_strsplit(username, ",", 2);
4642 userserver = g_strsplit(signinname_login[0], "@", 2);
4643 purple_connection_set_display_name(gc, userserver[0]);
4644 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4645 sip->sipdomain = g_strdup(userserver[1]);
4647 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4648 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4649 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4651 sip->password = g_strdup(purple_connection_get_password(gc));
4653 g_strfreev(userserver);
4654 g_strfreev(domain_user);
4655 g_strfreev(signinname_login);
4657 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4659 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4661 /* TODO: Set the status correctly. */
4662 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4664 transport = purple_account_get_string(account, "transport", "auto");
4665 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4666 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4667 SIPE_TRANSPORT_UDP;
4669 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4670 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4671 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4672 } else if (strcmp(transport, "auto") == 0) {
4673 sip->auto_transport = TRUE;
4674 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4675 } else if (strcmp(transport, "tls") == 0) {
4676 resolve_next_service(sip, service_tls);
4677 } else if (strcmp(transport, "tcp") == 0) {
4678 resolve_next_service(sip, service_tcp);
4679 } else {
4680 resolve_next_service(sip, service_udp);
4684 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4686 connection_free_all(sip);
4688 g_free(sip->epid);
4689 sip->epid = NULL;
4691 if (sip->query_data != NULL)
4692 purple_dnsquery_destroy(sip->query_data);
4693 sip->query_data = NULL;
4695 if (sip->srv_query_data != NULL)
4696 purple_srv_cancel(sip->srv_query_data);
4697 sip->srv_query_data = NULL;
4699 if (sip->listen_data != NULL)
4700 purple_network_listen_cancel(sip->listen_data);
4701 sip->listen_data = NULL;
4703 if (sip->gsc != NULL)
4704 purple_ssl_close(sip->gsc);
4705 sip->gsc = NULL;
4707 sipe_auth_free(&sip->registrar);
4708 sipe_auth_free(&sip->proxy);
4710 if (sip->txbuf)
4711 purple_circ_buffer_destroy(sip->txbuf);
4712 sip->txbuf = NULL;
4714 g_free(sip->realhostname);
4715 sip->realhostname = NULL;
4717 if (sip->listenpa)
4718 purple_input_remove(sip->listenpa);
4719 sip->listenpa = 0;
4720 if (sip->tx_handler)
4721 purple_input_remove(sip->tx_handler);
4722 sip->tx_handler = 0;
4723 if (sip->resendtimeout)
4724 purple_timeout_remove(sip->resendtimeout);
4725 sip->resendtimeout = 0;
4726 if (sip->timeouts) {
4727 GSList *entry = sip->timeouts;
4728 while (entry) {
4729 struct scheduled_action *sched_action = entry->data;
4730 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4731 purple_timeout_remove(sched_action->timeout_handler);
4732 g_free(sched_action->payload);
4733 g_free(sched_action->name);
4734 g_free(sched_action);
4735 entry = entry->next;
4738 g_slist_free(sip->timeouts);
4740 g_slist_free(sip->allow_events);
4742 if (sip->contact)
4743 g_free(sip->contact);
4744 sip->contact = NULL;
4745 if (sip->regcallid)
4746 g_free(sip->regcallid);
4747 sip->regcallid = NULL;
4749 sip->fd = -1;
4750 sip->processing_input = FALSE;
4754 * A callback for g_hash_table_foreach_remove
4756 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4758 sipe_free_buddy(buddy);
4761 static void sipe_close(PurpleConnection *gc)
4763 struct sipe_account_data *sip = gc->proto_data;
4765 if (sip) {
4766 /* leave all conversations */
4767 im_session_close_all(sip);
4769 /* unregister */
4770 do_register_exp(sip, 0);
4772 sipe_connection_cleanup(sip);
4773 g_free(sip->sipdomain);
4774 g_free(sip->username);
4775 g_free(sip->password);
4776 g_free(sip->authdomain);
4777 g_free(sip->authuser);
4778 g_free(sip->status);
4780 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4781 g_hash_table_destroy(sip->buddies);
4783 g_free(gc->proto_data);
4784 gc->proto_data = NULL;
4787 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4789 PurpleAccount *acct = purple_connection_get_account(gc);
4790 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4791 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4792 if (conv == NULL)
4793 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4794 purple_conversation_present(conv);
4795 g_free(id);
4798 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4801 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4802 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4805 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4807 PurpleNotifySearchResults *results;
4808 PurpleNotifySearchColumn *column;
4809 xmlnode *searchResults;
4810 xmlnode *mrow;
4811 int match_count = 0;
4812 gboolean more = FALSE;
4813 gchar *secondary;
4815 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
4817 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4818 if (!searchResults) {
4819 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4820 return FALSE;
4823 results = purple_notify_searchresults_new();
4825 if (results == NULL) {
4826 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4827 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4829 xmlnode_free(searchResults);
4830 return FALSE;
4833 column = purple_notify_searchresults_column_new(_("User Name"));
4834 purple_notify_searchresults_column_add(results, column);
4836 column = purple_notify_searchresults_column_new(_("Name"));
4837 purple_notify_searchresults_column_add(results, column);
4839 column = purple_notify_searchresults_column_new(_("Company"));
4840 purple_notify_searchresults_column_add(results, column);
4842 column = purple_notify_searchresults_column_new(_("Country"));
4843 purple_notify_searchresults_column_add(results, column);
4845 column = purple_notify_searchresults_column_new(_("Email"));
4846 purple_notify_searchresults_column_add(results, column);
4848 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4849 GList *row = NULL;
4851 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4852 row = g_list_append(row, g_strdup(uri_parts[1]));
4853 g_strfreev(uri_parts);
4855 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4856 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4857 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4858 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4860 purple_notify_searchresults_row_add(results, row);
4861 match_count++;
4864 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4865 char *data = xmlnode_get_data_unescaped(mrow);
4866 more = (g_strcasecmp(data, "true") == 0);
4867 g_free(data);
4870 secondary = g_strdup_printf(
4871 dngettext(GETTEXT_PACKAGE,
4872 "Found %d contact%s:",
4873 "Found %d contacts%s:", match_count),
4874 match_count, more ? _(" (more matched your query)") : "");
4876 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4877 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4878 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4880 g_free(secondary);
4881 xmlnode_free(searchResults);
4882 return TRUE;
4885 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4887 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4888 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4889 unsigned i = 0;
4891 do {
4892 PurpleRequestField *field = entries->data;
4893 const char *id = purple_request_field_get_id(field);
4894 const char *value = purple_request_field_string_get_value(field);
4896 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
4898 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
4899 } while ((entries = g_list_next(entries)) != NULL);
4900 attrs[i] = NULL;
4902 if (i > 0) {
4903 gchar *query = g_strjoinv(NULL, attrs);
4904 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
4905 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
4906 send_soap_request_with_cb(gc->proto_data, body,
4907 (TransCallback) process_search_contact_response, NULL);
4908 g_free(body);
4909 g_free(query);
4912 g_strfreev(attrs);
4915 static void sipe_show_find_contact(PurplePluginAction *action)
4917 PurpleConnection *gc = (PurpleConnection *) action->context;
4918 PurpleRequestFields *fields;
4919 PurpleRequestFieldGroup *group;
4920 PurpleRequestField *field;
4922 fields = purple_request_fields_new();
4923 group = purple_request_field_group_new(NULL);
4924 purple_request_fields_add_group(fields, group);
4926 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4927 purple_request_field_group_add_field(group, field);
4928 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4929 purple_request_field_group_add_field(group, field);
4930 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4931 purple_request_field_group_add_field(group, field);
4932 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4933 purple_request_field_group_add_field(group, field);
4935 purple_request_fields(gc,
4936 _("Search"),
4937 _("Search for a Contact"),
4938 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4939 fields,
4940 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4941 _("_Cancel"), NULL,
4942 purple_connection_get_account(gc), NULL, NULL, gc);
4945 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4947 GList *menu = NULL;
4948 PurplePluginAction *act;
4950 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4951 menu = g_list_prepend(menu, act);
4953 menu = g_list_reverse(menu);
4955 return menu;
4958 static void dummy_permit_deny(PurpleConnection *gc)
4962 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4964 return TRUE;
4968 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4970 return TRUE;
4974 static char *sipe_status_text(PurpleBuddy *buddy)
4976 struct sipe_account_data *sip;
4977 struct sipe_buddy *sbuddy;
4978 char *text = NULL;
4980 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4981 if (sip) //happens on pidgin exit
4983 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4984 if (sbuddy && sbuddy->annotation)
4986 text = g_strdup(sbuddy->annotation);
4990 return text;
4993 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4995 const PurplePresence *presence = purple_buddy_get_presence(buddy);
4996 const PurpleStatus *status = purple_presence_get_active_status(presence);
4997 struct sipe_account_data *sip;
4998 struct sipe_buddy *sbuddy;
4999 char *annotation = NULL;
5001 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5002 if (sip) //happens on pidgin exit
5004 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5005 if (sbuddy)
5007 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5011 //Layout
5012 if (purple_presence_is_online(presence))
5014 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5017 if (annotation)
5019 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5020 g_free(annotation);
5025 static GHashTable *
5026 sipe_get_account_text_table(PurpleAccount *account)
5028 GHashTable *table;
5029 table = g_hash_table_new(g_str_hash, g_str_equal);
5030 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5031 return table;
5034 static PurpleBuddy *
5035 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5037 PurpleBuddy *clone;
5038 const gchar *server_alias, *email;
5039 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5041 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5043 purple_blist_add_buddy(clone, NULL, group, NULL);
5045 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5046 if (server_alias) {
5047 purple_blist_server_alias_buddy(clone, server_alias);
5050 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5051 if (email) {
5052 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5055 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5056 //for UI to update;
5057 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5058 return clone;
5061 static void
5062 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5064 PurpleBuddy *buddy, *b;
5065 PurpleConnection *gc;
5066 PurpleGroup * group = purple_find_group(group_name);
5068 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5070 buddy = (PurpleBuddy *)node;
5072 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5073 gc = purple_account_get_connection(buddy->account);
5075 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5076 if (!b){
5077 b = purple_blist_add_buddy_clone(group, buddy);
5080 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5083 static void
5084 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5086 const gchar *email;
5087 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5089 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5090 if (email)
5092 char *mailto = g_strdup_printf("mailto:%s", email);
5093 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5094 #ifndef _WIN32
5096 pid_t pid;
5097 char *const parmList[] = {mailto, NULL};
5098 if ((pid = fork()) == -1)
5100 purple_debug_info("sipe", "fork() error\n");
5102 else if (pid == 0)
5104 execvp("xdg-email", parmList);
5105 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5108 #else
5110 BOOL ret;
5111 _flushall();
5112 errno = 0;
5113 //@TODO resolve env variable %WINDIR% first
5114 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5115 if (errno)
5117 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5120 #endif
5122 g_free(mailto);
5124 else
5126 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5131 * A menu which appear when right-clicking on buddy in contact list.
5133 static GList *
5134 sipe_buddy_menu(PurpleBuddy *buddy)
5136 PurpleBlistNode *g_node;
5137 PurpleGroup *group, *gr_parent;
5138 PurpleMenuAction *act;
5139 GList *menu = NULL;
5140 GList *menu_groups = NULL;
5142 act = purple_menu_action_new(_("Send Email..."),
5143 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5144 NULL, NULL);
5145 menu = g_list_prepend(menu, act);
5147 gr_parent = purple_buddy_get_group(buddy);
5148 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5149 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5150 continue;
5152 group = (PurpleGroup *)g_node;
5153 if (group == gr_parent)
5154 continue;
5156 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5157 continue;
5159 act = purple_menu_action_new(purple_group_get_name(group),
5160 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5161 group->name, NULL);
5162 menu_groups = g_list_prepend(menu_groups, act);
5164 menu_groups = g_list_reverse(menu_groups);
5166 act = purple_menu_action_new(_("Copy to"),
5167 NULL,
5168 NULL, menu_groups);
5169 menu = g_list_prepend(menu, act);
5170 menu = g_list_reverse(menu);
5172 return menu;
5175 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5176 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5177 return sipe_buddy_menu((PurpleBuddy *) node);
5178 } else {
5179 return NULL;
5183 static gboolean
5184 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5186 gboolean ret = TRUE;
5187 char *username = (char *)trans->payload;
5189 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5190 PurpleBuddy *pbuddy;
5191 struct sipe_buddy *sbuddy;
5192 const char *alias;
5193 char *server_alias = NULL;
5194 char *email = NULL;
5195 const char *device_name = NULL;
5197 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5199 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5200 alias = purple_buddy_get_local_alias(pbuddy);
5202 if (sip)
5204 //will query buddy UA's capabilities and send answer to log
5205 sipe_options_request(sip, username);
5207 sbuddy = g_hash_table_lookup(sip->buddies, username);
5208 if (sbuddy)
5210 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5214 if (msg->response != 200) {
5215 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5216 } else {
5217 xmlnode *searchResults;
5218 xmlnode *mrow;
5220 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5221 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5222 if (!searchResults) {
5223 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5224 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5225 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5226 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5227 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5228 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5229 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5230 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5231 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5232 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5233 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5234 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5235 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5236 if (!email || strcmp("", email)) {
5237 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5238 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5242 xmlnode_free(searchResults);
5245 purple_notify_user_info_add_section_break(info);
5247 if (!server_alias || !strcmp("", server_alias)) {
5248 g_free(server_alias);
5249 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5250 if (server_alias) {
5251 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5255 // same as server alias, do not present
5256 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5257 if (alias)
5259 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5262 if (!email || !strcmp("", email)) {
5263 g_free(email);
5264 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5265 if (email) {
5266 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5270 if (device_name)
5272 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5275 /* show a buddy's user info in a nice dialog box */
5276 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5277 username, /* buddy's username */
5278 info, /* body */
5279 NULL, /* callback called when dialog closed */
5280 NULL); /* userdata for callback */
5282 return ret;
5286 * AD search first, LDAP based
5288 static void sipe_get_info(PurpleConnection *gc, const char *username)
5290 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5291 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5293 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5294 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5295 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5296 g_free(body);
5297 g_free(row);
5300 static PurplePlugin *my_protocol = NULL;
5302 static PurplePluginProtocolInfo prpl_info =
5305 NULL, /* user_splits */
5306 NULL, /* protocol_options */
5307 NO_BUDDY_ICONS, /* icon_spec */
5308 sipe_list_icon, /* list_icon */
5309 NULL, /* list_emblems */
5310 sipe_status_text, /* status_text */
5311 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5312 sipe_status_types, /* away_states */
5313 sipe_blist_node_menu, /* blist_node_menu */
5314 NULL, /* chat_info */
5315 NULL, /* chat_info_defaults */
5316 sipe_login, /* login */
5317 sipe_close, /* close */
5318 sipe_im_send, /* send_im */
5319 NULL, /* set_info */ // TODO maybe
5320 sipe_send_typing, /* send_typing */
5321 sipe_get_info, /* get_info */
5322 sipe_set_status, /* set_status */
5323 NULL, /* set_idle */
5324 NULL, /* change_passwd */
5325 sipe_add_buddy, /* add_buddy */
5326 NULL, /* add_buddies */
5327 sipe_remove_buddy, /* remove_buddy */
5328 NULL, /* remove_buddies */
5329 sipe_add_permit, /* add_permit */
5330 sipe_add_deny, /* add_deny */
5331 sipe_add_deny, /* rem_permit */
5332 sipe_add_permit, /* rem_deny */
5333 dummy_permit_deny, /* set_permit_deny */
5334 NULL, /* join_chat */
5335 NULL, /* reject_chat */
5336 NULL, /* get_chat_name */
5337 NULL, /* chat_invite */
5338 NULL, /* chat_leave */
5339 NULL, /* chat_whisper */
5340 NULL, /* chat_send */
5341 sipe_keep_alive, /* keepalive */
5342 NULL, /* register_user */
5343 NULL, /* get_cb_info */ // deprecated
5344 NULL, /* get_cb_away */ // deprecated
5345 sipe_alias_buddy, /* alias_buddy */
5346 sipe_group_buddy, /* group_buddy */
5347 sipe_rename_group, /* rename_group */
5348 NULL, /* buddy_free */
5349 sipe_convo_closed, /* convo_closed */
5350 purple_normalize_nocase, /* normalize */
5351 NULL, /* set_buddy_icon */
5352 sipe_remove_group, /* remove_group */
5353 NULL, /* get_cb_real_name */ // TODO?
5354 NULL, /* set_chat_topic */
5355 NULL, /* find_blist_chat */
5356 NULL, /* roomlist_get_list */
5357 NULL, /* roomlist_cancel */
5358 NULL, /* roomlist_expand_category */
5359 NULL, /* can_receive_file */
5360 NULL, /* send_file */
5361 NULL, /* new_xfer */
5362 NULL, /* offline_message */
5363 NULL, /* whiteboard_prpl_ops */
5364 sipe_send_raw, /* send_raw */
5365 NULL, /* roomlist_room_serialize */
5366 NULL, /* unregister_user */
5367 NULL, /* send_attention */
5368 NULL, /* get_attention_types */
5370 sizeof(PurplePluginProtocolInfo), /* struct_size */
5371 sipe_get_account_text_table, /* get_account_text_table */
5375 static PurplePluginInfo info = {
5376 PURPLE_PLUGIN_MAGIC,
5377 PURPLE_MAJOR_VERSION,
5378 PURPLE_MINOR_VERSION,
5379 PURPLE_PLUGIN_PROTOCOL, /**< type */
5380 NULL, /**< ui_requirement */
5381 0, /**< flags */
5382 NULL, /**< dependencies */
5383 PURPLE_PRIORITY_DEFAULT, /**< priority */
5384 "prpl-sipe", /**< id */
5385 "Microsoft LCS/OCS", /**< name */
5386 VERSION, /**< version */
5387 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5388 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5389 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5390 "Gabriel Burt <gburt@novell.com>", /**< author */
5391 PURPLE_WEBSITE, /**< homepage */
5392 sipe_plugin_load, /**< load */
5393 sipe_plugin_unload, /**< unload */
5394 sipe_plugin_destroy, /**< destroy */
5395 NULL, /**< ui_info */
5396 &prpl_info, /**< extra_info */
5397 NULL,
5398 sipe_actions,
5399 NULL,
5400 NULL,
5401 NULL,
5402 NULL
5405 static void sipe_plugin_destroy(PurplePlugin *plugin)
5407 GList *entry;
5409 entry = prpl_info.protocol_options;
5410 while (entry) {
5411 purple_account_option_destroy(entry->data);
5412 entry = g_list_delete_link(entry, entry);
5414 prpl_info.protocol_options = NULL;
5416 entry = prpl_info.user_splits;
5417 while (entry) {
5418 purple_account_user_split_destroy(entry->data);
5419 entry = g_list_delete_link(entry, entry);
5421 prpl_info.user_splits = NULL;
5424 static void init_plugin(PurplePlugin *plugin)
5426 PurpleAccountUserSplit *split;
5427 PurpleAccountOption *option;
5429 #ifdef ENABLE_NLS
5430 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5431 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5432 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5433 #endif
5435 purple_plugin_register(plugin);
5437 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5438 purple_account_user_split_set_reverse(split, FALSE);
5439 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5441 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5442 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5443 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5444 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5446 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5447 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5448 // Translators: noun (networking port)
5449 option = purple_account_option_int_new(_("Port"), "port", 5061);
5450 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5452 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5453 purple_account_option_add_list_item(option, _("Auto"), "auto");
5454 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5455 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5456 purple_account_option_add_list_item(option, _("UDP"), "udp");
5457 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5459 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5460 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5462 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5463 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5465 // TODO commented out so won't show in the preferences until we fix krb message signing
5466 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5467 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5469 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5470 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5471 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5474 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5475 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5476 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5477 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5478 my_protocol = plugin;
5481 /* I had to redefined the function for it load, but works */
5482 gboolean purple_init_plugin(PurplePlugin *plugin){
5483 plugin->info = &(info);
5484 init_plugin((plugin));
5485 sipe_plugin_load((plugin));
5486 return purple_plugin_register(plugin);