Merge branch 'mob' of git+ssh://mob@repo.or.cz/srv/git/siplcs into mob
[siplcs.git] / src / sipe.c
blob935e943d74fcf4a9403d97c32d6738daab44a0fd
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 static char *gentag()
93 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
96 static gchar *get_epid()
98 return sipe_uuid_get_macaddr();
101 static char *genbranch()
103 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
104 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
105 rand() & 0xFFFF, rand() & 0xFFFF);
108 static char *gencallid()
110 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
111 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
112 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
113 rand() & 0xFFFF, rand() & 0xFFFF);
116 static gchar *find_tag(const gchar *hdr)
118 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
119 if (!tag) {
120 // In case it's at the end and there's no trailing ;
121 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
123 return tag;
127 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
129 return "sipe";
132 static void sipe_plugin_destroy(PurplePlugin *plugin);
134 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
136 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
137 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
138 gpointer data);
140 static void sipe_close(PurpleConnection *gc);
142 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name);
143 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip);
144 static void send_presence_info(struct sipe_account_data *sip);
146 static void sendout_pkt(PurpleConnection *gc, const char *buf);
148 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
150 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
151 if (timeout != NULL) {
152 sscanf(timeout, "%u", &sip->keepalive_timeout);
153 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
154 sip->keepalive_timeout);
156 g_free(timeout);
159 static void sipe_keep_alive(PurpleConnection *gc)
161 struct sipe_account_data *sip = gc->proto_data;
162 if (sip->transport == SIPE_TRANSPORT_UDP) {
163 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
164 gchar buf[2] = {0, 0};
165 purple_debug_info("sipe", "sending keep alive\n");
166 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
167 } else {
168 time_t now = time(NULL);
169 if ((sip->keepalive_timeout > 0) &&
170 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
171 #if PURPLE_VERSION_CHECK(2,4,0)
172 && ((now - gc->last_received) >= sip->keepalive_timeout)
173 #endif
175 purple_debug_info("sipe", "sending keep alive\n");
176 sendout_pkt(gc, "\r\n\r\n");
177 sip->last_keepalive = now;
182 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
184 struct sip_connection *ret = NULL;
185 GSList *entry = sip->openconns;
186 while (entry) {
187 ret = entry->data;
188 if (ret->fd == fd) return ret;
189 entry = entry->next;
191 return NULL;
194 static void sipe_auth_free(struct sip_auth *auth)
196 g_free(auth->nonce);
197 auth->nonce = NULL;
198 g_free(auth->opaque);
199 auth->opaque = NULL;
200 g_free(auth->realm);
201 auth->realm = NULL;
202 g_free(auth->target);
203 auth->target = NULL;
204 g_free(auth->digest_session_key);
205 auth->digest_session_key = NULL;
206 g_free(auth->ntlm_key);
207 auth->ntlm_key = NULL;
208 auth->type = AUTH_TYPE_UNSET;
209 auth->retries = 0;
210 auth->expires = 0;
213 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
215 struct sip_connection *ret = g_new0(struct sip_connection, 1);
216 ret->fd = fd;
217 sip->openconns = g_slist_append(sip->openconns, ret);
218 return ret;
221 static void connection_remove(struct sipe_account_data *sip, int fd)
223 struct sip_connection *conn = connection_find(sip, fd);
224 if (conn) {
225 sip->openconns = g_slist_remove(sip->openconns, conn);
226 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
227 g_free(conn->inbuf);
228 g_free(conn);
232 static void connection_free_all(struct sipe_account_data *sip)
234 struct sip_connection *ret = NULL;
235 GSList *entry = sip->openconns;
236 while (entry) {
237 ret = entry->data;
238 connection_remove(sip, ret->fd);
239 entry = sip->openconns;
243 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
245 const gchar *method = msg->method;
246 const gchar *target = msg->target;
247 gchar noncecount[9];
248 gchar *response;
249 gchar *ret;
250 gchar *tmp = NULL;
251 const char *authdomain = sip->authdomain;
252 const char *authuser = sip->authuser;
253 //const char *krb5_realm;
254 const char *host;
255 //gchar *krb5_token = NULL;
257 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
258 // and do error checking
260 // KRB realm should always be uppercase
261 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
263 if (sip->realhostname) {
264 host = sip->realhostname;
265 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
266 host = purple_account_get_string(sip->account, "proxy", "");
267 } else {
268 host = sip->sipdomain;
271 /*gboolean new_auth = krb5_auth.gss_context == NULL;
272 if (new_auth) {
273 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
276 if (new_auth || force_reauth) {
277 krb5_token = krb5_auth.base64_token;
280 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
281 krb5_token = krb5_auth.base64_token;*/
283 if (!authdomain) {
284 authdomain = "";
287 if (!authuser || strlen(authuser) < 1) {
288 authuser = sip->username;
291 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
292 sprintf(noncecount, "%08d", auth->nc++);
293 response = purple_cipher_http_digest_calculate_response(
294 "md5", method, target, NULL, NULL,
295 auth->nonce, noncecount, NULL, auth->digest_session_key);
296 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
298 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);
299 g_free(response);
300 return ret;
301 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
302 // If we have a signature for the message, include that
303 if (msg->signature) {
304 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);
305 return tmp;
308 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
309 const gchar *ntlm_key;
310 gchar *gssapi_data;
311 #if GLIB_CHECK_VERSION(2,8,0)
312 const gchar * hostname = g_get_host_name();
313 #else
314 static char hostname[256];
315 int ret = gethostname(hostname, sizeof(hostname));
316 hostname[sizeof(hostname) - 1] = '\0';
317 if (ret == -1 || hostname[0] == '\0') {
318 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
319 g_strerror(errno);
320 strcpy(hostname, "localhost");
322 #endif
323 /*const gchar * hostname = purple_get_host_name();*/
325 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
326 auth->ntlm_key = (gchar *)ntlm_key;
327 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
328 g_free(gssapi_data);
329 return tmp;
332 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
333 return tmp;
334 } else if (auth->type == AUTH_TYPE_KERBEROS) {
335 /* Kerberos */
336 if (auth->nc == 3) {
337 /*if (new_auth || force_reauth) {
338 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
339 if (auth->opaque) {
340 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);
341 } else {
342 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
344 } else {
345 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
346 gchar * mic = "MICTODO";
347 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
348 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
349 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
350 //auth->opaque ? auth->opaque : "", auth->target);
351 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
352 //g_free(mic);
354 return tmp;
356 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
359 sprintf(noncecount, "%08d", auth->nc++);
360 response = purple_cipher_http_digest_calculate_response(
361 "md5", method, target, NULL, NULL,
362 auth->nonce, noncecount, NULL, auth->digest_session_key);
363 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
365 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);
366 g_free(response);
367 return ret;
370 static char *parse_attribute(const char *attrname, const char *source)
372 const char *tmp, *tmp2;
373 char *retval = NULL;
374 int len = strlen(attrname);
376 if (!strncmp(source, attrname, len)) {
377 tmp = source + len;
378 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
379 if (tmp2)
380 retval = g_strndup(tmp, tmp2 - tmp);
381 else
382 retval = g_strdup(tmp);
385 return retval;
388 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
390 int i = 0;
391 const char *authuser;
392 char *tmp;
393 gchar **parts;
394 //const char *krb5_realm;
395 //const char *host;
397 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
398 // and do error checking
400 // KRB realm should always be uppercase
401 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
403 if (sip->realhostname) {
404 host = sip->realhostname;
405 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
406 host = purple_account_get_string(sip->account, "proxy", "");
407 } else {
408 host = sip->sipdomain;
411 authuser = sip->authuser;
413 if (!authuser || strlen(authuser) < 1) {
414 authuser = sip->username;
417 if (!hdr) {
418 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
419 return;
422 if (!g_strncasecmp(hdr, "NTLM", 4)) {
423 auth->type = AUTH_TYPE_NTLM;
424 parts = g_strsplit(hdr+5, "\", ", 0);
425 i = 0;
426 while (parts[i]) {
427 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
428 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
429 g_free(auth->nonce);
430 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
431 g_free(tmp);
433 if ((tmp = parse_attribute("targetname=\"",
434 parts[i]))) {
435 g_free(auth->target);
436 auth->target = tmp;
438 else if ((tmp = parse_attribute("realm=\"",
439 parts[i]))) {
440 g_free(auth->realm);
441 auth->realm = tmp;
443 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
444 g_free(auth->opaque);
445 auth->opaque = tmp;
447 i++;
449 g_strfreev(parts);
450 auth->nc = 1;
451 if (!strstr(hdr, "gssapi-data")) {
452 auth->nc = 1;
453 } else {
454 auth->nc = 3;
456 return;
459 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
460 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
461 auth->type = AUTH_TYPE_KERBEROS;
462 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
463 parts = g_strsplit(hdr+9, "\", ", 0);
464 i = 0;
465 while (parts[i]) {
466 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
467 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
468 /*if (krb5_auth.gss_context == NULL) {
469 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
471 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
472 g_free(tmp);
474 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
475 g_free(auth->target);
476 auth->target = tmp;
477 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
478 g_free(auth->realm);
479 auth->realm = tmp;
480 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
481 g_free(auth->opaque);
482 auth->opaque = tmp;
484 i++;
486 g_strfreev(parts);
487 auth->nc = 3;
488 return;
491 auth->type = AUTH_TYPE_DIGEST;
492 parts = g_strsplit(hdr, " ", 0);
493 while (parts[i]) {
494 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
495 g_free(auth->nonce);
496 auth->nonce = tmp;
498 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
499 g_free(auth->realm);
500 auth->realm = tmp;
502 i++;
504 g_strfreev(parts);
506 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
507 if (auth->realm) {
508 g_free(auth->digest_session_key);
509 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
510 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
512 auth->nc = 1;
516 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
518 PurpleConnection *gc = data;
519 struct sipe_account_data *sip = gc->proto_data;
520 gsize max_write;
521 gssize written;
523 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
525 if (max_write == 0) {
526 if (sip->tx_handler != 0){
527 purple_input_remove(sip->tx_handler);
528 sip->tx_handler = 0;
530 return;
533 written = write(sip->fd, sip->txbuf->outptr, max_write);
535 if (written < 0 && errno == EAGAIN)
536 written = 0;
537 else if (written <= 0) {
538 /*TODO: do we really want to disconnect on a failure to write?*/
539 purple_connection_error(gc, _("Could not write"));
540 return;
543 purple_circ_buffer_mark_read(sip->txbuf, written);
546 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
548 PurpleConnection *gc = data;
549 struct sipe_account_data *sip = gc->proto_data;
550 gsize max_write;
551 gssize written;
553 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
555 if (max_write == 0) {
556 if (sip->tx_handler != 0) {
557 purple_input_remove(sip->tx_handler);
558 sip->tx_handler = 0;
559 return;
563 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
565 if (written < 0 && errno == EAGAIN)
566 written = 0;
567 else if (written <= 0) {
568 /*TODO: do we really want to disconnect on a failure to write?*/
569 purple_connection_error(gc, _("Could not write"));
570 return;
573 purple_circ_buffer_mark_read(sip->txbuf, written);
576 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
578 static void send_later_cb(gpointer data, gint source, const gchar *error)
580 PurpleConnection *gc = data;
581 struct sipe_account_data *sip;
582 struct sip_connection *conn;
584 if (!PURPLE_CONNECTION_IS_VALID(gc))
586 if (source >= 0)
587 close(source);
588 return;
591 if (source < 0) {
592 purple_connection_error(gc, _("Could not connect"));
593 return;
596 sip = gc->proto_data;
597 sip->fd = source;
598 sip->connecting = FALSE;
599 sip->last_keepalive = time(NULL);
601 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
603 /* If there is more to write now, we need to register a handler */
604 if (sip->txbuf->bufused > 0)
605 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
607 conn = connection_create(sip, source);
608 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
611 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
613 struct sipe_account_data *sip;
614 struct sip_connection *conn;
616 if (!PURPLE_CONNECTION_IS_VALID(gc))
618 if (gsc) purple_ssl_close(gsc);
619 return NULL;
622 sip = gc->proto_data;
623 sip->fd = gsc->fd;
624 sip->gsc = gsc;
625 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
626 sip->connecting = FALSE;
627 sip->last_keepalive = time(NULL);
629 conn = connection_create(sip, gsc->fd);
631 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
633 return sip;
636 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
638 PurpleConnection *gc = data;
639 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
640 if (sip == NULL) return;
642 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
644 /* If there is more to write now */
645 if (sip->txbuf->bufused > 0) {
646 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
651 static void sendlater(PurpleConnection *gc, const char *buf)
653 struct sipe_account_data *sip = gc->proto_data;
655 if (!sip->connecting) {
656 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
657 if (sip->transport == SIPE_TRANSPORT_TLS){
658 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
659 } else {
660 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
661 purple_connection_error(gc, _("Couldn't create socket"));
664 sip->connecting = TRUE;
667 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
668 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
670 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
673 static void sendout_pkt(PurpleConnection *gc, const char *buf)
675 struct sipe_account_data *sip = gc->proto_data;
676 time_t currtime = time(NULL);
677 int writelen = strlen(buf);
679 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
680 if (sip->transport == SIPE_TRANSPORT_UDP) {
681 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
682 purple_debug_info("sipe", "could not send packet\n");
684 } else {
685 int ret;
686 if (sip->fd < 0) {
687 sendlater(gc, buf);
688 return;
691 if (sip->tx_handler) {
692 ret = -1;
693 errno = EAGAIN;
694 } else{
695 if (sip->gsc){
696 ret = purple_ssl_write(sip->gsc, buf, writelen);
697 }else{
698 ret = write(sip->fd, buf, writelen);
702 if (ret < 0 && errno == EAGAIN)
703 ret = 0;
704 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
705 sendlater(gc, buf);
706 return;
709 if (ret < writelen) {
710 if (!sip->tx_handler){
711 if (sip->gsc){
712 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
714 else{
715 sip->tx_handler = purple_input_add(sip->fd,
716 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
717 gc);
721 /* XXX: is it OK to do this? You might get part of a request sent
722 with part of another. */
723 if (sip->txbuf->bufused > 0)
724 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
726 purple_circ_buffer_append(sip->txbuf, buf + ret,
727 writelen - ret);
732 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
734 sendout_pkt(gc, buf);
735 return len;
738 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
740 GSList *tmp = msg->headers;
741 gchar *name;
742 gchar *value;
743 GString *outstr = g_string_new("");
744 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
745 while (tmp) {
746 name = ((struct siphdrelement*) (tmp->data))->name;
747 value = ((struct siphdrelement*) (tmp->data))->value;
748 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
749 tmp = g_slist_next(tmp);
751 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
752 sendout_pkt(sip->gc, outstr->str);
753 g_string_free(outstr, TRUE);
756 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
758 gchar * buf;
759 if (sip->registrar.ntlm_key) {
760 struct sipmsg_breakdown msgbd;
761 gchar *signature_input_str;
762 msgbd.msg = msg;
763 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
764 msgbd.rand = g_strdup_printf("%08x", g_random_int());
765 sip->registrar.ntlm_num++;
766 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
767 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
768 if (signature_input_str != NULL) {
769 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
770 msg->rand = g_strdup(msgbd.rand);
771 msg->num = g_strdup(msgbd.num);
772 g_free(signature_input_str);
774 sipmsg_breakdown_free(&msgbd);
777 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
778 buf = auth_header(sip, &sip->registrar, msg);
779 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
780 sipmsg_add_header(msg, "Authorization", buf);
781 } else {
782 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
784 g_free(buf);
785 } 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")) {
786 sip->registrar.nc = 3;
787 sip->registrar.type = AUTH_TYPE_NTLM;
789 buf = auth_header(sip, &sip->registrar, msg);
790 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
791 g_free(buf);
792 } else {
793 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
797 static char *get_contact(struct sipe_account_data *sip)
799 return g_strdup(sip->contact);
803 * unused. Needed?
804 static char *get_contact_service(struct sipe_account_data *sip)
806 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()));
807 //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);
811 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
812 const char *text, const char *body)
814 gchar *name;
815 gchar *value;
816 GString *outstr = g_string_new("");
817 struct sipe_account_data *sip = gc->proto_data;
818 gchar *contact;
819 GSList *tmp;
821 sipmsg_remove_header(msg, "ms-user-data");
823 contact = get_contact(sip);
824 sipmsg_remove_header(msg, "Contact");
825 sipmsg_add_header(msg, "Contact", contact);
826 g_free(contact);
828 /* When sending the acknowlegements and errors, the content length from the original
829 message is still here, but there is no body; we need to make sure we're sending the
830 correct content length */
831 sipmsg_remove_header(msg, "Content-Length");
832 if (body) {
833 gchar len[12];
834 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
835 sipmsg_add_header(msg, "Content-Length", len);
836 } else {
837 sipmsg_remove_header(msg, "Content-Type");
838 sipmsg_add_header(msg, "Content-Length", "0");
841 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
842 //gchar * mic = "MICTODO";
843 msg->response = code;
845 sipmsg_remove_header(msg, "Authentication-Info");
846 sign_outgoing_message(msg, sip, msg->method);
848 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
849 tmp = msg->headers;
850 while (tmp) {
851 name = ((struct siphdrelement*) (tmp->data))->name;
852 value = ((struct siphdrelement*) (tmp->data))->value;
854 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
855 tmp = g_slist_next(tmp);
857 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
858 sendout_pkt(gc, outstr->str);
859 g_string_free(outstr, TRUE);
862 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
864 if (trans->msg) sipmsg_free(trans->msg);
865 sip->transactions = g_slist_remove(sip->transactions, trans);
866 g_free(trans);
869 static struct transaction *
870 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
872 struct transaction *trans = g_new0(struct transaction, 1);
873 trans->time = time(NULL);
874 trans->msg = (struct sipmsg *)msg;
875 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
876 trans->callback = callback;
877 sip->transactions = g_slist_append(sip->transactions, trans);
878 return trans;
881 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
883 struct transaction *trans;
884 GSList *transactions = sip->transactions;
885 gchar *cseq = sipmsg_find_header(msg, "CSeq");
887 while (transactions) {
888 trans = transactions->data;
889 if (!strcmp(trans->cseq, cseq)) {
890 return trans;
892 transactions = transactions->next;
895 return NULL;
898 static struct transaction *
899 send_sip_request(PurpleConnection *gc, const gchar *method,
900 const gchar *url, const gchar *to, const gchar *addheaders,
901 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
903 struct sipe_account_data *sip = gc->proto_data;
904 const char *addh = "";
905 char *buf;
906 struct sipmsg *msg;
907 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
908 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
909 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
910 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
911 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
912 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
913 gchar *route = strdup("");
914 gchar *epid = get_epid(); // TODO generate one per account/login
915 struct transaction *trans;
917 if (dialog && dialog->routes)
919 GSList *iter = dialog->routes;
921 while(iter)
923 char *tmp = route;
924 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
925 g_free(tmp);
926 iter = g_slist_next(iter);
930 if (!ourtag && !dialog) {
931 ourtag = gentag();
934 if (!strcmp(method, "REGISTER")) {
935 if (sip->regcallid) {
936 g_free(callid);
937 callid = g_strdup(sip->regcallid);
938 } else {
939 sip->regcallid = g_strdup(callid);
943 if (addheaders) addh = addheaders;
945 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
946 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
947 "From: <sip:%s>%s%s;epid=%s\r\n"
948 "To: <%s>%s%s%s%s\r\n"
949 "Max-Forwards: 70\r\n"
950 "CSeq: %d %s\r\n"
951 "User-Agent: %s\r\n"
952 "Call-ID: %s\r\n"
953 "%s%s"
954 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
955 method,
956 dialog && dialog->request ? dialog->request : url,
957 TRANSPORT_DESCRIPTOR,
958 purple_network_get_my_ip(-1),
959 sip->listenport,
960 branch ? ";branch=" : "",
961 branch ? branch : "",
962 sip->username,
963 ourtag ? ";tag=" : "",
964 ourtag ? ourtag : "",
965 epid,
967 theirtag ? ";tag=" : "",
968 theirtag ? theirtag : "",
969 theirepid ? ";epid=" : "",
970 theirepid ? theirepid : "",
971 dialog ? ++dialog->cseq : ++sip->cseq,
972 method,
973 useragent,
974 callid,
975 route,
976 addh,
977 body ? strlen(body) : 0,
978 body ? body : "");
981 //printf ("parsing msg buf:\n%s\n\n", buf);
982 msg = sipmsg_parse_msg(buf);
984 g_free(buf);
985 g_free(ourtag);
986 g_free(theirtag);
987 g_free(theirepid);
988 g_free(branch);
989 g_free(callid);
990 g_free(route);
991 g_free(epid);
993 sign_outgoing_message (msg, sip, method);
995 buf = sipmsg_to_string (msg);
997 /* add to ongoing transactions */
998 trans = transactions_add_buf(sip, msg, tc);
999 sendout_pkt(gc, buf);
1000 g_free(buf);
1002 return trans;
1005 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1007 gchar *from = g_strdup_printf("sip:%s", sip->username);
1008 gchar *contact = get_contact(sip);
1009 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1010 "Content-Type: application/SOAP+xml\r\n",contact);
1012 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1013 tr->payload = payload;
1015 g_free(from);
1016 g_free(contact);
1017 g_free(hdr);
1020 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1022 send_soap_request_with_cb(sip, body, NULL, NULL);
1025 static char *get_contact_register(struct sipe_account_data *sip)
1027 char *epid = get_epid();
1028 char *uuid = generateUUIDfromEPID(epid);
1029 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);
1030 g_free(uuid);
1031 g_free(epid);
1032 return(buf);
1035 static void do_register_exp(struct sipe_account_data *sip, int expire)
1037 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1038 char *to = g_strdup_printf("sip:%s", sip->username);
1039 char *contact = get_contact_register(sip);
1040 char *hdr = g_strdup_printf("Contact: %s\r\n"
1041 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1042 "Event: registration\r\n"
1043 "Allow-Events: presence\r\n"
1044 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1045 "Expires: %d\r\n", contact,expire);
1046 g_free(contact);
1048 sip->registerstatus = 1;
1050 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1051 process_register_response);
1053 g_free(hdr);
1054 g_free(uri);
1055 g_free(to);
1058 static void do_register_cb(struct sipe_account_data *sip)
1060 do_register_exp(sip, sip->registerexpire);
1061 sip->reregister_set = FALSE;
1064 static void do_register(struct sipe_account_data *sip)
1066 do_register_exp(sip, sip->registerexpire);
1070 * Returns URI from provided To or From header.
1072 * Needs to g_free() after use.
1074 * @return URI with sip: prefix
1076 static gchar *parse_from(const gchar *hdr)
1078 gchar *from;
1079 const gchar *tmp, *tmp2 = hdr;
1081 if (!hdr) return NULL;
1082 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1083 tmp = strchr(hdr, '<');
1085 /* i hate the different SIP UA behaviours... */
1086 if (tmp) { /* sip address in <...> */
1087 tmp2 = tmp + 1;
1088 tmp = strchr(tmp2, '>');
1089 if (tmp) {
1090 from = g_strndup(tmp2, tmp - tmp2);
1091 } else {
1092 purple_debug_info("sipe", "found < without > in From\n");
1093 return NULL;
1095 } else {
1096 tmp = strchr(tmp2, ';');
1097 if (tmp) {
1098 from = g_strndup(tmp2, tmp - tmp2);
1099 } else {
1100 from = g_strdup(tmp2);
1103 purple_debug_info("sipe", "got %s\n", from);
1104 return from;
1107 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1109 va_list args;
1110 xmlnode * node = NULL;
1111 const gchar * name;
1113 va_start(args, parent);
1114 while ((name = va_arg(args, const char *)) != NULL) {
1115 node = xmlnode_get_child(parent, name);
1116 if (node == NULL) return NULL;
1117 parent = node;
1119 va_end(args);
1121 return node;
1125 static void
1126 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1128 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1129 send_soap_request(sip, body);
1130 g_free(body);
1133 static void
1134 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1136 if (allow) {
1137 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1138 } else {
1139 purple_debug_info("sipe", "Blocking contact %s\n", who);
1142 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1145 static
1146 void sipe_auth_user_cb(void * data)
1148 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1149 if (!job) return;
1151 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1152 g_free(job);
1155 static
1156 void sipe_deny_user_cb(void * data)
1158 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1159 if (!job) return;
1161 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1162 g_free(job);
1165 static void
1166 sipe_add_permit(PurpleConnection *gc, const char *name)
1168 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1169 sipe_contact_allow_deny(sip, name, TRUE);
1172 static void
1173 sipe_add_deny(PurpleConnection *gc, const char *name)
1175 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1176 sipe_contact_allow_deny(sip, name, FALSE);
1179 /*static void
1180 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1182 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1183 sipe_contact_set_acl(sip, name, "");
1186 static void
1187 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1189 xmlnode *watchers;
1190 xmlnode *watcher;
1191 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1192 if (msg->response != 0 && msg->response != 200) return;
1194 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1196 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1197 if (!watchers) return;
1199 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1200 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1201 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1202 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1204 // TODO pull out optional displayName to pass as alias
1205 if (remote_user) {
1206 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1207 job->who = remote_user;
1208 job->sip = sip;
1209 purple_account_request_authorization(
1210 sip->account,
1211 remote_user,
1212 NULL, // id
1213 alias,
1214 NULL, // message
1215 on_list,
1216 sipe_auth_user_cb,
1217 sipe_deny_user_cb,
1218 (void *) job);
1223 xmlnode_free(watchers);
1224 return;
1227 static void
1228 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1230 PurpleGroup * purple_group = purple_find_group(group->name);
1231 if (!purple_group) {
1232 purple_group = purple_group_new(group->name);
1233 purple_blist_add_group(purple_group, NULL);
1236 if (purple_group) {
1237 group->purple_group = purple_group;
1238 sip->groups = g_slist_append(sip->groups, group);
1239 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1240 } else {
1241 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1245 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1247 struct sipe_group *group;
1248 GSList *entry;
1249 if (sip == NULL) {
1250 return NULL;
1253 entry = sip->groups;
1254 while (entry) {
1255 group = entry->data;
1256 if (group->id == id) {
1257 return group;
1259 entry = entry->next;
1261 return NULL;
1264 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1266 struct sipe_group *group;
1267 GSList *entry;
1268 if (sip == NULL) {
1269 return NULL;
1272 entry = sip->groups;
1273 while (entry) {
1274 group = entry->data;
1275 if (!strcmp(group->name, name)) {
1276 return group;
1278 entry = entry->next;
1280 return NULL;
1283 static void
1284 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1286 gchar *body;
1287 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1288 body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1289 send_soap_request(sip, body);
1290 g_free(body);
1291 g_free(group->name);
1292 group->name = g_strdup(name);
1296 * Only appends if no such value already stored.
1297 * Like Set in Java.
1299 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1300 GSList * res = list;
1301 if (!g_slist_find_custom(list, data, func)) {
1302 res = g_slist_insert_sorted(list, data, func);
1304 return res;
1307 static int
1308 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1309 return group1->id - group2->id;
1313 * Returns string like "2 4 7 8" - group ids buddy belong to.
1315 static gchar *
1316 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1317 int i = 0;
1318 gchar *res;
1319 //creating array from GList, converting int to gchar*
1320 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1321 GSList *entry = buddy->groups;
1322 while (entry) {
1323 struct sipe_group * group = entry->data;
1324 ids_arr[i] = g_strdup_printf("%d", group->id);
1325 entry = entry->next;
1326 i++;
1328 ids_arr[i] = NULL;
1329 res = g_strjoinv(" ", ids_arr);
1330 g_strfreev(ids_arr);
1331 return res;
1335 * Sends buddy update to server
1337 static void
1338 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1340 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1341 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1343 if (buddy && purple_buddy) {
1344 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1345 gchar *body;
1346 gchar *groups = sipe_get_buddy_groups_string(buddy);
1347 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1349 body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1350 alias, groups, "true", buddy->name, sip->contacts_delta++
1352 send_soap_request(sip, body);
1353 g_free(groups);
1354 g_free(body);
1358 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1360 if (msg->response == 200) {
1361 struct sipe_group *group;
1362 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1363 xmlnode *xml;
1364 xmlnode *node;
1365 char *group_id;
1366 struct sipe_buddy *buddy;
1367 group->name = ctx->group_name;
1369 xml = xmlnode_from_str(msg->body, msg->bodylen);
1370 if (!xml) {
1371 g_free(ctx);
1372 return FALSE;
1375 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1376 if (!node) {
1377 g_free(ctx);
1378 xmlnode_free(xml);
1379 return FALSE;
1382 group_id = xmlnode_get_data(node);
1383 if (!group_id) {
1384 g_free(ctx);
1385 xmlnode_free(xml);
1386 return FALSE;
1389 group = g_new0(struct sipe_group, 1);
1390 group->id = (int)g_ascii_strtod(group_id, NULL);
1391 g_free(group_id);
1393 sipe_group_add(sip, group);
1395 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1396 if (buddy) {
1397 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1400 sipe_group_set_user(sip, ctx->user_name);
1402 g_free(ctx);
1403 xmlnode_free(xml);
1404 return TRUE;
1406 return FALSE;
1409 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1411 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1412 gchar *body;
1413 ctx->group_name = g_strdup(name);
1414 ctx->user_name = g_strdup(who);
1416 body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1417 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1418 g_free(body);
1422 * A timer callback
1423 * Should return FALSE if repetitive action is not needed
1425 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1427 gboolean ret;
1428 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1429 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1430 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1431 (sched_action->action)(sched_action->sip, sched_action->payload);
1432 ret = sched_action->repetitive;
1433 g_free(sched_action->payload);
1434 g_free(sched_action->name);
1435 g_free(sched_action);
1436 return ret;
1440 * Do schedule action for execution in the future.
1441 * Non repetitive execution.
1443 * @param name of action (will be copied)
1444 * @param timeout in seconds
1445 * @action callback function
1446 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1448 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1450 struct scheduled_action *sched_action;
1452 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1453 sched_action = g_new0(struct scheduled_action, 1);
1454 sched_action->repetitive = FALSE;
1455 sched_action->name = g_strdup(name);
1456 sched_action->action = action;
1457 sched_action->sip = sip;
1458 sched_action->payload = payload;
1459 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1460 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1461 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1465 * Kills action timer effectively cancelling
1466 * scheduled action
1468 * @param name of action
1470 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1472 GSList *entry;
1474 if (!sip->timeouts || !name) return;
1476 entry = sip->timeouts;
1477 while (entry) {
1478 struct scheduled_action *sched_action = entry->data;
1479 if(!strcmp(sched_action->name, name)) {
1480 GSList *to_delete = entry;
1481 entry = entry->next;
1482 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1483 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1484 purple_timeout_remove(sched_action->timeout_handler);
1485 g_free(sched_action->payload);
1486 g_free(sched_action->name);
1487 g_free(sched_action);
1488 } else {
1489 entry = entry->next;
1494 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1496 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1498 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1500 process_incoming_notify(sip, msg, FALSE, FALSE);
1502 return TRUE;
1505 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1507 gchar *tmp = *resources_uri;
1508 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1509 g_free(tmp);
1513 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1514 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1515 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1516 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1517 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1520 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip){
1521 gchar *to = g_strdup_printf("sip:%s", sip->username);
1522 gchar *contact = get_contact(sip);
1523 gchar *request;
1524 gchar *content;
1525 gchar *resources_uri = g_strdup("");
1526 gchar *require = "";
1527 gchar *accept = "";
1528 gchar *content_type;
1530 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1532 if (sip->msrtc_event_categories) {
1533 require = ", categoryList";
1534 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1535 content_type = "application/msrtc-adrl-categorylist+xml";
1536 content = g_strdup_printf(
1537 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1538 "<action name=\"subscribe\" id=\"63792024\">\n"
1539 "<adhocList>\n%s</adhocList>\n"
1540 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1541 "<category name=\"note\"/>\n"
1542 "<category name=\"state\"/>\n"
1543 "</categoryList>\n"
1544 "</action>\n"
1545 "</batchSub>", sip->username, resources_uri);
1546 } else {
1547 content_type = "application/adrl+xml";
1548 content = g_strdup_printf(
1549 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1550 "<create xmlns=\"\">\n%s</create>\n"
1551 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1553 g_free(resources_uri);
1555 request = g_strdup_printf(
1556 "Require: adhoclist%s\r\n"
1557 "Supported: eventlist\r\n"
1558 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1559 "Supported: ms-piggyback-first-notify\r\n"
1560 "Supported: com.microsoft.autoextend\r\n"
1561 "Supported: ms-benotify\r\n"
1562 "Proxy-Require: ms-benotify\r\n"
1563 "Event: presence\r\n"
1564 "Content-Type: %s\r\n"
1565 "Contact: %s\r\n", require, accept, content_type, contact);
1566 g_free(contact);
1568 /* subscribe to buddy presence */
1569 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1570 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1572 g_free(content);
1573 g_free(to);
1574 g_free(request);
1578 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1579 * The user sends a single SUBSCRIBE request to the subscribed contact.
1580 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1584 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1586 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1587 gchar *tmp = get_contact(sip);
1588 gchar *request;
1589 gchar *content;
1590 request = g_strdup_printf(
1591 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1592 "Supported: ms-piggyback-first-notify\r\n"
1593 "Supported: com.microsoft.autoextend\r\n"
1594 "Supported: ms-benotify\r\n"
1595 "Proxy-Require: ms-benotify\r\n"
1596 "Event: presence\r\n"
1597 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1598 "Contact: %s\r\n", tmp);
1600 content = g_strdup_printf(
1601 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1602 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1603 "<resource uri=\"%s\"/>\n"
1604 "</adhocList>\n"
1605 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1606 "<category name=\"note\"/>\n"
1607 "<category name=\"state\"/>\n"
1608 "</categoryList>\n"
1609 "</action>\n"
1610 "</batchSub>", sip->username, to
1613 g_free(tmp);
1615 /* subscribe to buddy presence */
1616 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1618 g_free(content);
1619 g_free(to);
1620 g_free(request);
1623 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1625 if (!purple_status_is_active(status))
1626 return;
1628 if (account->gc) {
1629 struct sipe_account_data *sip = account->gc->proto_data;
1631 if (sip) {
1632 g_free(sip->status);
1633 sip->status = g_strdup(purple_status_get_id(status));
1634 send_presence_info(sip);
1639 static void
1640 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1642 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1643 sipe_group_set_user(sip, name);
1646 static void
1647 sipe_group_buddy(PurpleConnection *gc,
1648 const char *who,
1649 const char *old_group_name,
1650 const char *new_group_name)
1652 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1653 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1654 struct sipe_group * old_group = NULL;
1655 struct sipe_group * new_group;
1657 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1658 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1660 if(!buddy) { // buddy not in roaming list
1661 return;
1664 if (old_group_name) {
1665 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1667 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1669 if (old_group) {
1670 buddy->groups = g_slist_remove(buddy->groups, old_group);
1671 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1674 if (!new_group) {
1675 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1676 } else {
1677 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1678 sipe_group_set_user(sip, who);
1682 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1684 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1685 struct sipe_buddy *b;
1687 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1689 // Prepend sip: if needed
1690 if (strncmp("sip:", buddy->name, 4)) {
1691 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1692 purple_blist_rename_buddy(buddy, buf);
1693 g_free(buf);
1696 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1697 b = g_new0(struct sipe_buddy, 1);
1698 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1699 b->name = g_strdup(buddy->name);
1700 g_hash_table_insert(sip->buddies, b->name, b);
1701 sipe_group_buddy(gc, b->name, NULL, group->name);
1702 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1703 } else {
1704 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1708 static void sipe_free_buddy(struct sipe_buddy *buddy)
1710 g_free(buddy->name);
1711 g_free(buddy->annotation);
1712 g_free(buddy->device_name);
1713 g_slist_free(buddy->groups);
1714 g_free(buddy);
1718 * Unassociates buddy from group first.
1719 * Then see if no groups left, removes buddy completely.
1720 * Otherwise updates buddy groups on server.
1722 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1724 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1725 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1726 struct sipe_group *g = NULL;
1728 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1730 if (!b) return;
1732 if (group) {
1733 g = sipe_group_find_by_name(sip, group->name);
1736 if (g) {
1737 b->groups = g_slist_remove(b->groups, g);
1738 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1741 if (g_slist_length(b->groups) < 1) {
1742 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1743 sipe_cancel_scheduled_action(sip, action_name);
1744 g_free(action_name);
1746 g_hash_table_remove(sip->buddies, buddy->name);
1748 if (b->name) {
1749 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1750 send_soap_request(sip, body);
1751 g_free(body);
1754 sipe_free_buddy(b);
1755 } else {
1756 //updates groups on server
1757 sipe_group_set_user(sip, b->name);
1762 static void
1763 sipe_rename_group(PurpleConnection *gc,
1764 const char *old_name,
1765 PurpleGroup *group,
1766 GList *moved_buddies)
1768 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1769 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1770 if (group) {
1771 sipe_group_rename(sip, s_group, group->name);
1772 } else {
1773 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1777 static void
1778 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1780 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1781 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1782 if (s_group) {
1783 gchar *body;
1784 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1785 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1786 send_soap_request(sip, body);
1787 g_free(body);
1789 sip->groups = g_slist_remove(sip->groups, s_group);
1790 g_free(s_group->name);
1791 } else {
1792 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1796 static GList *sipe_status_types(PurpleAccount *acc)
1798 PurpleStatusType *type;
1799 GList *types = NULL;
1801 // Online
1802 type = purple_status_type_new_with_attrs(
1803 PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE,
1804 // Translators: noun
1805 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1806 NULL);
1807 types = g_list_append(types, type);
1809 // Busy
1810 type = purple_status_type_new_with_attrs(
1811 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1812 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1813 NULL);
1814 types = g_list_append(types, type);
1816 // Do Not Disturb (Not let user set it)
1817 type = purple_status_type_new_with_attrs(
1818 PURPLE_STATUS_UNAVAILABLE, "do-not-disturb", "Do Not Disturb", TRUE, FALSE, FALSE,
1819 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1820 NULL);
1821 types = g_list_append(types, type);
1823 // Be Right Back
1824 type = purple_status_type_new_with_attrs(
1825 PURPLE_STATUS_AWAY, "be-right-back", _("Be Right Back"), TRUE, TRUE, FALSE,
1826 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1827 NULL);
1828 types = g_list_append(types, type);
1830 // Away
1831 type = purple_status_type_new_with_attrs(
1832 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1833 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1834 NULL);
1835 types = g_list_append(types, type);
1837 //On The Phone
1838 type = purple_status_type_new_with_attrs(
1839 PURPLE_STATUS_UNAVAILABLE, "on-the-phone", _("On The Phone"), TRUE, TRUE, FALSE,
1840 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1841 NULL);
1842 types = g_list_append(types, type);
1844 //Out To Lunch
1845 type = purple_status_type_new_with_attrs(
1846 PURPLE_STATUS_AWAY, "out-to-lunch", "Out To Lunch", TRUE, TRUE, FALSE,
1847 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1848 NULL);
1849 types = g_list_append(types, type);
1851 //Appear Offline
1852 type = purple_status_type_new_full(
1853 PURPLE_STATUS_INVISIBLE, NULL, "Appear Offline", TRUE, TRUE, FALSE);
1854 types = g_list_append(types, type);
1856 // Offline
1857 type = purple_status_type_new_full(
1858 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1859 types = g_list_append(types, type);
1861 return types;
1865 * A callback for g_hash_table_foreach
1867 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1869 sipe_subscribe_presence_single(sip, buddy->name);
1873 * Removes entries from purple buddy list
1874 * that does not correspond ones in the roaming contact list.
1876 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1877 GSList *buddies = purple_find_buddies(sip->account, NULL);
1878 GSList *entry = buddies;
1879 struct sipe_buddy *buddy;
1880 PurpleBuddy *b;
1881 PurpleGroup *g;
1883 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1884 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1885 while (entry) {
1886 b = entry->data;
1887 g = purple_buddy_get_group(b);
1888 buddy = g_hash_table_lookup(sip->buddies, b->name);
1889 if(buddy) {
1890 gboolean in_sipe_groups = FALSE;
1891 GSList *entry2 = buddy->groups;
1892 while (entry2) {
1893 struct sipe_group *group = entry2->data;
1894 if (!strcmp(group->name, g->name)) {
1895 in_sipe_groups = TRUE;
1896 break;
1898 entry2 = entry2->next;
1900 if(!in_sipe_groups) {
1901 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1902 purple_blist_remove_buddy(b);
1904 } else {
1905 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1906 purple_blist_remove_buddy(b);
1908 entry = entry->next;
1912 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1914 int len = msg->bodylen;
1916 gchar *tmp = sipmsg_find_header(msg, "Event");
1917 xmlnode *item;
1918 xmlnode *isc;
1919 const gchar *contacts_delta;
1920 xmlnode *group_node;
1921 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1922 return FALSE;
1925 /* Convert the contact from XML to Purple Buddies */
1926 isc = xmlnode_from_str(msg->body, len);
1927 if (!isc) {
1928 return FALSE;
1931 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1932 if (contacts_delta) {
1933 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1936 /* Parse groups */
1937 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1938 struct sipe_group * group = g_new0(struct sipe_group, 1);
1939 const char *name = xmlnode_get_attrib(group_node, "name");
1941 if (!strncmp(name, "~", 1)) {
1942 // TODO translate
1943 name = "Other Contacts";
1945 group->name = g_strdup(name);
1946 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1948 sipe_group_add(sip, group);
1951 // Make sure we have at least one group
1952 if (g_slist_length(sip->groups) == 0) {
1953 struct sipe_group * group = g_new0(struct sipe_group, 1);
1954 PurpleGroup *purple_group;
1955 // TODO translate
1956 group->name = g_strdup("Other Contacts");
1957 group->id = 1;
1958 purple_group = purple_group_new(group->name);
1959 purple_blist_add_group(purple_group, NULL);
1960 sip->groups = g_slist_append(sip->groups, group);
1963 /* Parse contacts */
1964 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1965 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1966 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1967 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1968 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1969 gchar **item_groups;
1970 struct sipe_group *group = NULL;
1971 struct sipe_buddy *buddy = NULL;
1972 int i = 0;
1974 // assign to group Other Contacts if nothing else received
1975 if(!groups || !strcmp("", groups) ) {
1976 group = sipe_group_find_by_name(sip, "Other Contacts");
1977 groups = group ? g_strdup_printf("%d", group->id) : "1";
1980 item_groups = g_strsplit(groups, " ", 0);
1982 while (item_groups[i]) {
1983 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1985 // If couldn't find the right group for this contact, just put them in the first group we have
1986 if (group == NULL && g_slist_length(sip->groups) > 0) {
1987 group = sip->groups->data;
1990 if (group != NULL) {
1991 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1992 if (!b){
1993 b = purple_buddy_new(sip->account, buddy_name, uri);
1994 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1997 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1998 if (name != NULL && strlen(name) != 0) {
1999 purple_blist_alias_buddy(b, name);
2003 if (!buddy) {
2004 buddy = g_new0(struct sipe_buddy, 1);
2005 buddy->name = g_strdup(b->name);
2006 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2009 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2011 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2012 } else {
2013 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2014 name);
2017 i++;
2018 } // while, contact groups
2019 g_strfreev(item_groups);
2020 g_free(groups);
2021 g_free(name);
2022 g_free(buddy_name);
2023 g_free(uri);
2025 } // for, contacts
2027 xmlnode_free(isc);
2029 sipe_cleanup_local_blist(sip);
2031 //subscribe to buddies
2032 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2033 //if(sip->msrtc_event_categories){
2034 sipe_subscribe_presence_batched(sip);
2035 //}else{
2036 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2038 sip->subscribed_buddies = TRUE;
2041 return 0;
2045 * Subscribe roaming contacts
2047 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2049 gchar *to = g_strdup_printf("sip:%s", sip->username);
2050 gchar *tmp = get_contact(sip);
2051 gchar *hdr = g_strdup_printf(
2052 "Event: vnd-microsoft-roaming-contacts\r\n"
2053 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2054 "Supported: com.microsoft.autoextend\r\n"
2055 "Supported: ms-benotify\r\n"
2056 "Proxy-Require: ms-benotify\r\n"
2057 "Supported: ms-piggyback-first-notify\r\n"
2058 "Contact: %s\r\n", tmp);
2059 g_free(tmp);
2061 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2062 g_free(to);
2063 g_free(hdr);
2066 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2068 gchar *to = g_strdup_printf("sip:%s", sip->username);
2069 gchar *tmp = get_contact(sip);
2070 gchar *hdr = g_strdup_printf(
2071 "Event: presence.wpending\r\n"
2072 "Accept: text/xml+msrtc.wpending\r\n"
2073 "Supported: com.microsoft.autoextend\r\n"
2074 "Supported: ms-benotify\r\n"
2075 "Proxy-Require: ms-benotify\r\n"
2076 "Supported: ms-piggyback-first-notify\r\n"
2077 "Contact: %s\r\n", tmp);
2078 g_free(tmp);
2080 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2081 g_free(to);
2082 g_free(hdr);
2085 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2087 const gchar *contacts_delta;
2088 xmlnode *xml;
2090 xml = xmlnode_from_str(msg->body, msg->bodylen);
2091 if (!xml)
2093 return;
2096 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2097 if (contacts_delta)
2099 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2102 xmlnode_free(xml);
2106 * When we receive some self (BE) NOTIFY with a new subscriber
2107 * we sends a setSubscribers request to him [SIP-PRES]
2111 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2113 gchar *contact;
2114 gchar *to;
2115 xmlnode *xml;
2116 xmlnode *node;
2118 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2120 xml = xmlnode_from_str(msg->body, msg->bodylen);
2121 if (!xml) return;
2123 contact = get_contact(sip);
2124 to = g_strdup_printf("sip:%s", sip->username);
2126 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2127 const char *user;
2128 gchar *hdr;
2129 gchar *body;
2131 user = xmlnode_get_attrib(node, "user");
2132 if (!user) continue;
2134 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2136 hdr = g_strdup_printf(
2137 "Contact: %s\r\n"
2138 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2140 body = g_strdup_printf(
2141 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2142 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2143 "</setSubscribers>", user);
2145 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2146 g_free(body);
2147 g_free(hdr);
2150 g_free(to);
2151 g_free(contact);
2152 xmlnode_free(xml);
2155 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2157 gchar *to = g_strdup_printf("sip:%s", sip->username);
2158 gchar *tmp = get_contact(sip);
2159 gchar *hdr = g_strdup_printf(
2160 "Event: vnd-microsoft-roaming-ACL\r\n"
2161 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2162 "Supported: com.microsoft.autoextend\r\n"
2163 "Supported: ms-benotify\r\n"
2164 "Proxy-Require: ms-benotify\r\n"
2165 "Supported: ms-piggyback-first-notify\r\n"
2166 "Contact: %s\r\n", tmp);
2167 g_free(tmp);
2169 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2170 g_free(to);
2171 g_free(hdr);
2175 * To request for presence information about the user, access level settings that have already been configured by the user
2176 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2177 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2180 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2182 gchar *to = g_strdup_printf("sip:%s", sip->username);
2183 gchar *tmp = get_contact(sip);
2184 gchar *hdr = g_strdup_printf(
2185 "Event: vnd-microsoft-roaming-self\r\n"
2186 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2187 "Supported: com.microsoft.autoextend\r\n"
2188 "Supported: ms-benotify\r\n"
2189 "Proxy-Require: ms-benotify\r\n"
2190 "Supported: ms-piggyback-first-notify\r\n"
2191 "Contact: %s\r\n"
2192 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2194 gchar *body=g_strdup(
2195 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2196 "<roaming type=\"categories\"/>"
2197 "<roaming type=\"containers\"/>"
2198 "<roaming type=\"subscribers\"/></roamingList>");
2200 g_free(tmp);
2201 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2202 g_free(body);
2203 g_free(to);
2204 g_free(hdr);
2207 /** Subscription for provisioning information to help with initial
2208 * configuration. This subscription is a one-time query (denoted by the Expires header,
2209 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2210 * configuration, meeting policies, and policy settings that Communicator must enforce.
2211 * TODO: for what we need this information.
2214 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2216 gchar *to = g_strdup_printf("sip:%s", sip->username);
2217 gchar *tmp = get_contact(sip);
2218 gchar *hdr = g_strdup_printf(
2219 "Event: vnd-microsoft-provisioning-v2\r\n"
2220 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2221 "Supported: com.microsoft.autoextend\r\n"
2222 "Supported: ms-benotify\r\n"
2223 "Proxy-Require: ms-benotify\r\n"
2224 "Supported: ms-piggyback-first-notify\r\n"
2225 "Expires: 0\r\n"
2226 "Contact: %s\r\n"
2227 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2228 gchar *body = g_strdup(
2229 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2230 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2231 "<provisioningGroup name=\"ucPolicy\"/>"
2232 "</provisioningGroupList>");
2234 g_free(tmp);
2235 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2236 g_free(body);
2237 g_free(to);
2238 g_free(hdr);
2241 /* IM Session (INVITE and MESSAGE methods) */
2243 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2245 struct sip_im_session *session;
2246 GSList *entry;
2247 if (sip == NULL || who == NULL) {
2248 return NULL;
2251 entry = sip->im_sessions;
2252 while (entry) {
2253 session = entry->data;
2254 if ((who != NULL && !strcmp(who, session->with))) {
2255 return session;
2257 entry = entry->next;
2259 return NULL;
2262 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2264 struct sip_im_session *session = find_im_session(sip, who);
2265 if (!session) {
2266 session = g_new0(struct sip_im_session, 1);
2267 session->with = g_strdup(who);
2268 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2270 return session;
2273 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2275 struct sip_dialog *dialog = session->dialog;
2276 GSList *entry;
2278 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2280 if (dialog) {
2281 entry = dialog->routes;
2282 while (entry) {
2283 g_free(entry->data);
2284 entry = g_slist_remove(entry, entry->data);
2286 entry = dialog->supported;
2287 while (entry) {
2288 g_free(entry->data);
2289 entry = g_slist_remove(entry, entry->data);
2291 g_free(dialog->callid);
2292 g_free(dialog->ourtag);
2293 g_free(dialog->theirtag);
2294 g_free(dialog->theirepid);
2295 g_free(dialog->request);
2297 g_free(session->dialog);
2299 entry = session->outgoing_message_queue;
2300 while (entry) {
2301 g_free(entry->data);
2302 entry = g_slist_remove(entry, entry->data);
2305 g_free(session->with);
2306 g_free(session);
2309 static gboolean
2310 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2312 gboolean ret = TRUE;
2314 if (msg->response != 200) {
2315 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2316 return FALSE;
2319 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2321 return ret;
2325 * Asks UA/proxy about its capabilities.
2327 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2329 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2330 gchar *contact = get_contact(sip);
2331 gchar *request;
2332 request = g_strdup_printf(
2333 "Accept: application/sdp\r\n"
2334 "Contact: %s\r\n", contact);
2336 g_free(contact);
2338 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2340 g_free(to);
2341 g_free(request);
2344 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2346 char *msg, *msg_tmp;
2347 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2348 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2349 g_free(msg_tmp);
2350 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2351 "possibly because one or more persons are offline:\n%s") ,
2352 msg ? msg : "");
2353 purple_conv_present_error(with, sip->account, msg_tmp);
2354 g_free(msg);
2355 g_free(msg_tmp);
2358 static void sipe_im_remove_first_from_queue (struct sip_im_session * session);
2359 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2361 static gboolean
2362 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2364 gboolean ret = TRUE;
2365 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2366 struct sip_im_session * session = find_im_session(sip, with);
2367 struct sip_dialog *dialog;
2369 if (!session) {
2370 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2371 g_free(with);
2372 return FALSE;
2375 if (msg->response != 200) {
2376 gchar *queued_msg = NULL;
2377 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2379 if (session->outgoing_message_queue) {
2380 queued_msg = session->outgoing_message_queue->data;
2382 sipe_present_message_undelivered_err(with, sip, queued_msg);
2383 im_session_destroy(sip, session);
2384 g_free(with);
2385 return FALSE;
2388 dialog = session->dialog;
2389 if (!dialog) {
2390 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2391 ret = FALSE;
2394 sipe_im_remove_first_from_queue(session);
2395 sipe_im_process_queue(sip, session);
2396 g_free(with);
2397 return ret;
2400 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2402 gchar *hdr;
2403 gchar *fullto;
2404 gchar *tmp;
2405 char *msgformat;
2406 char *msgtext;
2407 gchar *msgr_value;
2408 gchar *msgr;
2410 if (strncmp("sip:", session->with, 4)) {
2411 fullto = g_strdup_printf("sip:%s", session->with);
2412 } else {
2413 fullto = g_strdup(session->with);
2416 sipe_parse_html(msg, &msgformat, &msgtext);
2417 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2419 msgr_value = sipmsg_get_msgr_string(msgformat);
2420 g_free(msgformat);
2421 if (msgr_value) {
2422 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2423 g_free(msgr_value);
2424 } else {
2425 msgr = g_strdup("");
2428 tmp = get_contact(sip);
2429 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2430 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2431 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2432 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2433 tmp, msgr);
2434 g_free(tmp);
2435 g_free(msgr);
2437 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2438 g_free(msgtext);
2439 g_free(hdr);
2440 g_free(fullto);
2444 static void
2445 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2447 GSList *entry = session->outgoing_message_queue;
2448 if (entry) {
2449 char *queued_msg = entry->data;
2450 sipe_send_message(sip, session, queued_msg);
2454 static void
2455 sipe_im_remove_first_from_queue (struct sip_im_session * session)
2457 if (session && session->outgoing_message_queue) {
2458 char *queued_msg = session->outgoing_message_queue->data;
2459 // Remove from the queue and free the string
2460 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2461 g_free(queued_msg);
2465 static void
2466 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2468 GSList *hdr = msg->headers;
2469 struct siphdrelement *elem;
2470 gchar *contact;
2472 while(hdr)
2474 elem = hdr->data;
2475 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2477 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2478 dialog->routes = g_slist_append(dialog->routes, route);
2480 hdr = g_slist_next(hdr);
2483 if (outgoing)
2485 dialog->routes = g_slist_reverse(dialog->routes);
2488 if (dialog->routes)
2490 dialog->request = dialog->routes->data;
2491 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2494 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2495 dialog->routes = g_slist_append(dialog->routes, contact);
2498 static void
2499 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2501 GSList *hdr = msg->headers;
2502 struct siphdrelement *elem;
2503 while(hdr)
2505 elem = hdr->data;
2506 if(!g_ascii_strcasecmp(elem->name, "Supported")
2507 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2509 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2512 hdr = g_slist_next(hdr);
2516 static void
2517 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2519 gchar *us = outgoing ? "From" : "To";
2520 gchar *them = outgoing ? "To" : "From";
2522 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2523 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2524 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2525 if (!dialog->theirepid) {
2526 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2528 if (!dialog->theirepid) {
2529 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2532 sipe_get_route_header(msg, dialog, outgoing);
2533 sipe_get_supported_header(msg, dialog, outgoing);
2537 static gboolean
2538 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2540 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2541 struct sip_im_session * session = find_im_session(sip, with);
2542 struct sip_dialog *dialog;
2544 if (!session) {
2545 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2546 g_free(with);
2547 return FALSE;
2550 if (msg->response != 200) {
2551 gchar *queued_msg = NULL;
2552 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2554 if (session->outgoing_message_queue) {
2555 queued_msg = session->outgoing_message_queue->data;
2557 sipe_present_message_undelivered_err(with, sip, queued_msg);
2559 im_session_destroy(sip, session);
2560 g_free(with);
2561 return FALSE;
2564 dialog = session->dialog;
2565 if (!dialog) {
2566 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2567 g_free(with);
2568 return FALSE;
2571 sipe_parse_dialog(msg, dialog, TRUE);
2572 dialog->cseq = 0;
2574 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2575 session->outgoing_invite = NULL;
2576 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2577 sipe_im_remove_first_from_queue(session);
2578 } else {
2579 sipe_im_process_queue(sip, session);
2582 g_free(with);
2583 return TRUE;
2587 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
2589 gchar *hdr;
2590 gchar *to;
2591 gchar *contact;
2592 gchar *body;
2593 char *msgformat;
2594 char *msgtext;
2595 char *base64_msg;
2596 char *ms_text_format;
2597 gchar *msgr_value;
2598 gchar *msgr;
2600 if (session->dialog) {
2601 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2602 return;
2605 session->dialog = g_new0(struct sip_dialog, 1);
2607 if (strstr(session->with, "sip:")) {
2608 to = g_strdup(session->with);
2609 } else {
2610 to = g_strdup_printf("sip:%s", session->with);
2613 sipe_parse_html(msg_body, &msgformat, &msgtext);
2614 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2616 msgr_value = sipmsg_get_msgr_string(msgformat);
2617 g_free(msgformat);
2618 msgr = "";
2619 if (msgr_value) {
2620 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2621 g_free(msgr_value);
2624 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2625 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2626 g_free(msgtext);
2627 g_free(msgr);
2628 g_free(base64_msg);
2630 contact = get_contact(sip);
2631 hdr = g_strdup_printf(
2632 "Contact: %s\r\n%s"
2633 "Content-Type: application/sdp\r\n",
2634 contact, ms_text_format);
2635 g_free(ms_text_format);
2637 body = g_strdup_printf(
2638 "v=0\r\n"
2639 "o=- 0 0 IN IP4 %s\r\n"
2640 "s=session\r\n"
2641 "c=IN IP4 %s\r\n"
2642 "t=0 0\r\n"
2643 "m=message %d sip null\r\n"
2644 "a=accept-types:text/plain text/html image/gif "
2645 "multipart/alternative application/im-iscomposing+xml\r\n",
2646 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2648 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2649 to, to, hdr, body, session->dialog, process_invite_response);
2651 g_free(to);
2652 g_free(body);
2653 g_free(hdr);
2654 g_free(contact);
2657 static void
2658 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2660 if (session) {
2661 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2662 im_session_destroy(sip, session);
2666 static void
2667 sipe_convo_closed(PurpleConnection * gc, const char *who)
2669 struct sipe_account_data *sip = gc->proto_data;
2671 purple_debug_info("sipe", "conversation with %s closed\n", who);
2672 im_session_close(sip, find_im_session(sip, who));
2675 static void
2676 im_session_close_all (struct sipe_account_data *sip)
2678 GSList *entry = sip->im_sessions;
2679 while (entry) {
2680 im_session_close (sip, entry->data);
2681 entry = sip->im_sessions;
2685 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2687 struct sipe_account_data *sip;
2688 gchar *to;
2689 gchar *text;
2690 struct sip_im_session *session;
2692 sip = gc->proto_data;
2693 to = g_strdup(who);
2694 text = g_strdup(what);
2696 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2698 session = find_or_create_im_session(sip, who);
2700 // Queue the message
2701 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
2703 if (session->dialog && session->dialog->callid) {
2704 sipe_im_process_queue(sip, session);
2705 } else if (!session->outgoing_invite) {
2706 // Need to send the INVITE to get the outgoing dialog setup
2707 sipe_invite(sip, session, text);
2710 g_free(to);
2711 g_free(text);
2712 return 1;
2716 /* End IM Session (INVITE and MESSAGE methods) */
2718 static unsigned int
2719 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2721 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2722 struct sip_im_session *session;
2724 if (state == PURPLE_NOT_TYPING)
2725 return 0;
2727 session = find_im_session(sip, who);
2729 if (session && session->dialog) {
2730 send_sip_request(gc, "INFO", who, who,
2731 "Content-Type: application/xml\r\n",
2732 SIPE_SEND_TYPING, session->dialog, NULL);
2735 return SIPE_TYPING_SEND_TIMEOUT;
2738 static gboolean resend_timeout(struct sipe_account_data *sip)
2740 GSList *tmp = sip->transactions;
2741 time_t currtime = time(NULL);
2742 while (tmp) {
2743 struct transaction *trans = tmp->data;
2744 tmp = tmp->next;
2745 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2746 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2747 /* TODO 408 */
2748 } else {
2749 if ((currtime - trans->time > 2) && trans->retries == 0) {
2750 trans->retries++;
2751 sendout_sipmsg(sip, trans->msg);
2755 return TRUE;
2758 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2760 /* register again when security token expires */
2761 /* we have to start a new authentication as the security token
2762 * is almost expired by sending a not signed REGISTER message */
2763 purple_debug_info("sipe", "do a full reauthentication\n");
2764 sipe_auth_free(&sip->registrar);
2765 sip->registerstatus = 0;
2766 do_register(sip);
2767 sip->reauthenticate_set = FALSE;
2770 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2772 gchar *from;
2773 gchar *contenttype;
2774 gboolean found = FALSE;
2776 from = parse_from(sipmsg_find_header(msg, "From"));
2778 if (!from) return;
2780 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2782 contenttype = sipmsg_find_header(msg, "Content-Type");
2783 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2784 gchar *msgr = sipmsg_find_part_of_header(contenttype, "msgr=", NULL, NULL);
2785 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2787 gchar *body_esc = g_markup_escape_text(msg->body, -1);
2788 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2789 g_free(msgr);
2790 g_free(body_esc);
2791 g_free(x_mms_im_format);
2793 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2794 g_free(body_html);
2795 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2796 found = TRUE;
2797 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2798 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2799 xmlnode *state;
2800 gchar *statedata;
2802 if (!isc) {
2803 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2804 return;
2807 state = xmlnode_get_child(isc, "state");
2809 if (!state) {
2810 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2811 xmlnode_free(isc);
2812 return;
2815 statedata = xmlnode_get_data(state);
2816 if (statedata) {
2817 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2818 else serv_got_typing_stopped(sip->gc, from);
2820 g_free(statedata);
2822 xmlnode_free(isc);
2823 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2824 found = TRUE;
2826 if (!found) {
2827 purple_debug_info("sipe", "got unknown mime-type");
2828 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2830 g_free(from);
2833 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2835 gchar *ms_text_format;
2836 gchar *from;
2837 gchar *body;
2838 struct sip_im_session *session;
2840 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
2842 // Only accept text invitations
2843 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2844 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2845 return;
2848 from = parse_from(sipmsg_find_header(msg, "From"));
2849 session = find_or_create_im_session (sip, from);
2850 if (session) {
2851 if (session->dialog) {
2852 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2853 } else {
2854 session->dialog = g_new0(struct sip_dialog, 1);
2856 sipe_parse_dialog(msg, session->dialog, FALSE);
2858 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2859 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2860 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2861 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2863 } else {
2864 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2867 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2868 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2869 if (ms_text_format && !strncmp(ms_text_format, "text/plain", 10)) {
2870 gchar *msgr = sipmsg_find_part_of_header(ms_text_format, "msgr=", ";", NULL);
2871 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2873 gchar *ms_body = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
2874 g_free(msgr);
2875 if (ms_body) {
2876 gchar *body = purple_base64_decode(ms_body, NULL);
2877 gchar *body_esc = g_markup_escape_text(body, -1);
2878 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2879 g_free(ms_body);
2880 g_free(body_esc);
2881 g_free(body);
2882 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2883 g_free(body_html);
2884 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2886 g_free(x_mms_im_format);
2888 g_free(from);
2890 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2891 sipmsg_remove_header(msg, "Ms-Text-Format");
2892 sipmsg_remove_header(msg, "EndPoints");
2893 sipmsg_remove_header(msg, "User-Agent");
2894 sipmsg_remove_header(msg, "Roster-Manager");
2896 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2897 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2899 body = g_strdup_printf(
2900 "v=0\r\n"
2901 "o=- 0 0 IN IP4 %s\r\n"
2902 "s=session\r\n"
2903 "c=IN IP4 %s\r\n"
2904 "t=0 0\r\n"
2905 "m=message %d sip sip:%s\r\n"
2906 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2907 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2908 sip->realport, sip->username);
2909 send_sip_response(sip->gc, msg, 200, "OK", body);
2910 g_free(body);
2913 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
2915 gchar *from;
2916 gchar *body;
2917 struct sip_im_session *session;
2920 from = parse_from(sipmsg_find_header(msg, "From"));
2921 session = find_or_create_im_session (sip, from);
2922 if (session) {
2923 if (session->dialog) {
2924 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2925 } else {
2926 session->dialog = g_new0(struct sip_dialog, 1);
2928 sipe_parse_dialog(msg, session->dialog, FALSE);
2930 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2931 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2932 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2933 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2935 } else {
2936 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2939 g_free(from);
2941 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2942 sipmsg_remove_header(msg, "EndPoints");
2943 sipmsg_remove_header(msg, "User-Agent");
2945 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
2946 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2948 body = g_strdup_printf(
2949 "v=0\r\n"
2950 "o=- 0 0 IN IP4 0.0.0.0\r\n"
2951 "s=session\r\n"
2952 "c=IN IP4 0.0.0.0\r\n"
2953 "t=0 0\r\n"
2954 "m=message %d sip sip:%s\r\n"
2955 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2956 sip->realport, sip->username);
2957 send_sip_response(sip->gc, msg, 200, "OK", body);
2958 g_free(body);
2961 static void sipe_connection_cleanup(struct sipe_account_data *);
2962 static void create_connection(struct sipe_account_data *, gchar *, int);
2964 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2966 gchar *tmp;
2967 //gchar krb5_token;
2968 const gchar *expires_header;
2969 int expires;
2970 GSList *hdr = msg->headers;
2971 struct siphdrelement *elem;
2973 expires_header = sipmsg_find_header(msg, "Expires");
2974 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2975 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2977 switch (msg->response) {
2978 case 200:
2979 if (expires == 0) {
2980 sip->registerstatus = 0;
2981 } else {
2982 int i = 0;
2983 gchar *contact_hdr = NULL;
2984 gchar *gruu = NULL;
2985 gchar *epid;
2986 gchar *uuid;
2988 sip->registerexpire = expires;
2990 if (!sip->reregister_set) {
2991 gchar *action_name = g_strdup_printf("<%s>", "registration");
2992 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
2993 g_free(action_name);
2994 sip->reregister_set = TRUE;
2997 sip->registerstatus = 3;
2999 if (!sip->reauthenticate_set) {
3000 /* we have to reauthenticate as our security token expires
3001 after eight hours (be five minutes early) */
3002 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3003 guint reauth_timeout = (8 * 3600) - 360;
3004 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3005 g_free(action_name);
3006 sip->reauthenticate_set = TRUE;
3009 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3011 epid = get_epid();
3012 uuid = generateUUIDfromEPID(epid);
3013 g_free(epid);
3015 // There can be multiple Contact headers (one per location where the user is logged in) so
3016 // make sure to only get the one for this uuid
3017 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3018 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3019 if (valid_contact) {
3020 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3021 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3022 g_free(valid_contact);
3023 break;
3024 } else {
3025 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3028 g_free(uuid);
3030 g_free(sip->contact);
3031 if(gruu) {
3032 sip->contact = g_strdup_printf("<%s>", gruu);
3033 g_free(gruu);
3034 } else {
3035 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3036 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);
3038 sip->msrtc_event_categories = FALSE;
3040 while(hdr)
3042 elem = hdr->data;
3043 if(!g_ascii_strcasecmp(elem->name, "Supported"))
3045 if (strstr(elem->value, "msrtc-event-categories")){
3046 sip->msrtc_event_categories = TRUE;
3048 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s, %d\n", elem->value, sip->msrtc_event_categories);
3050 hdr = g_slist_next(hdr);
3053 if (!sip->subscribed) { //do it just once, not every re-register
3054 tmp = sipmsg_find_header(msg, "Allow-Events");
3055 sipe_options_request(sip, sip->sipdomain);
3056 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
3057 sipe_subscribe_roaming_contacts(sip, msg);
3059 sipe_subscribe_roaming_acl(sip, msg);
3060 sipe_subscribe_roaming_self(sip, msg);
3061 sipe_subscribe_roaming_provisioning(sip, msg);
3062 sipe_subscribe_presence_wpending(sip, msg);
3063 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3064 sip->subscribed = TRUE;
3067 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
3068 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
3069 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
3070 } else {
3071 tmp = sipmsg_find_header(msg, "ms-keep-alive");
3072 if (tmp) {
3073 sipe_keep_alive_timeout(sip, tmp);
3077 // Should we remove the transaction here?
3078 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3079 transactions_remove(sip, tc);
3081 break;
3082 case 301:
3084 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3086 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3087 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3088 gchar **tmp;
3089 gchar *hostname;
3090 int port = 0;
3091 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3092 int i = 1;
3094 tmp = g_strsplit(parts[0], ":", 0);
3095 hostname = g_strdup(tmp[0]);
3096 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3097 g_strfreev(tmp);
3099 while (parts[i]) {
3100 tmp = g_strsplit(parts[i], "=", 0);
3101 if (tmp[1]) {
3102 if (g_strcasecmp("transport", tmp[0]) == 0) {
3103 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3104 transport = SIPE_TRANSPORT_TCP;
3105 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3106 transport = SIPE_TRANSPORT_UDP;
3110 g_strfreev(tmp);
3111 i++;
3113 g_strfreev(parts);
3115 /* Close old connection */
3116 sipe_connection_cleanup(sip);
3118 /* Create new connection */
3119 sip->transport = transport;
3120 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3121 hostname, port, TRANSPORT_DESCRIPTOR);
3122 create_connection(sip, hostname, port);
3124 g_free(redirect);
3126 break;
3127 case 401:
3128 if (sip->registerstatus != 2) {
3129 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3130 if (sip->registrar.retries > 3) {
3131 sip->gc->wants_to_die = TRUE;
3132 purple_connection_error(sip->gc, _("Wrong Password"));
3133 return TRUE;
3135 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3136 tmp = sipmsg_find_auth_header(msg, "NTLM");
3137 } else {
3138 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3140 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3141 fill_auth(sip, tmp, &sip->registrar);
3142 sip->registerstatus = 2;
3143 if (sip->account->disconnecting) {
3144 do_register_exp(sip, 0);
3145 } else {
3146 do_register(sip);
3149 break;
3150 case 403:
3152 const gchar *warning = sipmsg_find_header(msg, "Warning");
3153 if (warning != NULL) {
3154 /* Example header:
3155 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3157 gchar **tmp = g_strsplit(warning, "\"", 0);
3158 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3159 g_strfreev(tmp);
3160 } else {
3161 warning = _("You have been rejected by the server");
3164 sip->gc->wants_to_die = TRUE;
3165 purple_connection_error(sip->gc, warning);
3166 return TRUE;
3168 break;
3169 case 404:
3171 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3172 if (warning != NULL) {
3173 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3174 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3175 g_free(reason);
3176 } else {
3177 warning = _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3180 sip->gc->wants_to_die = TRUE;
3181 purple_connection_error(sip->gc, warning);
3182 return TRUE;
3184 break;
3185 case 503:
3187 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3188 if (warning != NULL) {
3189 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3190 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3191 g_free(reason);
3192 } else {
3193 warning = _("Service unavailable: no reason given");
3196 sip->gc->wants_to_die = TRUE;
3197 purple_connection_error(sip->gc, warning);
3198 return TRUE;
3200 break;
3202 return TRUE;
3205 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3207 const char *uri;
3208 xmlnode *xn_categories;
3209 xmlnode *xn_category;
3210 xmlnode *xn_node;
3211 int changed = 0;
3212 const char *activity = NULL;
3214 xn_categories = xmlnode_from_str(data, len);
3215 uri = xmlnode_get_attrib(xn_categories, "uri");
3217 for (xn_category = xmlnode_get_child(xn_categories, "category");
3218 xn_category ;
3219 xn_category = xmlnode_get_next_twin(xn_category) )
3221 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3223 if (!strcmp(attrVar, "note"))
3225 char *note;
3226 struct sipe_buddy *sbuddy;
3227 xn_node = xmlnode_get_child(xn_category, "note");
3228 if (!xn_node) continue;
3229 xn_node = xmlnode_get_child(xn_node, "body");
3230 if (!xn_node) continue;
3232 note = xmlnode_get_data(xn_node);
3234 if(uri){
3235 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3237 if (sbuddy && note)
3239 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3240 sbuddy->annotation = g_strdup(note);
3241 changed = 1;
3244 g_free(note);
3246 else if(!strcmp(attrVar, "state"))
3248 char *data;
3249 int avail;
3250 xn_node = xmlnode_get_child(xn_category, "state");
3251 if (!xn_node) continue;
3252 xn_node = xmlnode_get_child(xn_node, "availability");
3253 if (!xn_node) continue;
3255 data = xmlnode_get_data(xn_node);
3256 avail = atoi(data);
3257 g_free(data);
3259 if (avail < 3000)
3260 activity = "unknown";
3261 else if (avail < 4500)
3262 activity = "available";
3263 else if (avail < 6000)
3264 activity = "idle";
3265 else if (avail < 7500)
3266 activity = "busy";
3267 else if (avail < 9000)
3268 activity = "busy";
3269 else if (avail < 12000)
3270 activity = "dnd";
3271 else if (avail < 18000)
3272 activity = "away";
3273 else
3274 activity = "offline";
3276 changed = 1;
3279 if (changed)
3281 if(activity){
3282 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n",activity);
3283 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3287 xmlnode_free(xn_categories);
3290 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3292 const char *uri,*state;
3293 xmlnode *xn_list;
3294 xmlnode *xn_resource;
3295 xmlnode *xn_instance;
3297 xn_list = xmlnode_from_str(data, len);
3299 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3300 xn_resource;
3301 xn_resource = xmlnode_get_next_twin(xn_resource) )
3303 xn_instance = xmlnode_get_child(xn_resource, "instance");
3304 if (!xn_instance) return;
3306 state = xmlnode_get_attrib(xn_instance, "state");
3307 uri = xmlnode_get_attrib(xn_instance, "cid");
3308 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3309 if(strstr(state,"resubscribe")){
3310 sipe_subscribe_presence_single(sip, uri);
3315 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3317 const gchar *uri;
3318 gchar *getbasic;
3319 gchar *activity = NULL;
3320 xmlnode *pidf;
3321 xmlnode *basicstatus = NULL, *tuple, *status;
3322 gboolean isonline = FALSE;
3323 xmlnode *display_name_node;
3325 pidf = xmlnode_from_str(data, len);
3326 if (!pidf) {
3327 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3328 return;
3331 uri = xmlnode_get_attrib(pidf, "entity");
3333 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3335 if ((status = xmlnode_get_child(tuple, "status"))) {
3336 basicstatus = xmlnode_get_child(status, "basic");
3340 if (!basicstatus) {
3341 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3342 xmlnode_free(pidf);
3343 return;
3346 getbasic = xmlnode_get_data(basicstatus);
3347 if (!getbasic) {
3348 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3349 xmlnode_free(pidf);
3350 return;
3353 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3354 if (strstr(getbasic, "open")) {
3355 isonline = TRUE;
3357 g_free(getbasic);
3359 display_name_node = xmlnode_get_child(pidf, "display-name");
3360 // updating display name if alias was just URI
3361 if (display_name_node) {
3362 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3363 GSList *entry = buddies;
3364 PurpleBuddy *p_buddy;
3365 char * display_name = xmlnode_get_data(display_name_node);
3367 while (entry) {
3368 const char *server_alias;
3369 char *alias;
3371 p_buddy = entry->data;
3373 alias = (char *)purple_buddy_get_alias(p_buddy);
3374 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3375 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3376 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3377 purple_blist_alias_buddy(p_buddy, display_name);
3379 g_free(alias);
3381 server_alias = purple_buddy_get_server_alias(p_buddy);
3382 if (display_name &&
3383 ( (server_alias && strcmp(display_name, server_alias))
3384 || !server_alias || strlen(server_alias) == 0 )
3386 purple_blist_server_alias_buddy(p_buddy, display_name);
3389 entry = entry->next;
3391 g_free(display_name);
3394 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3395 if ((status = xmlnode_get_child(tuple, "status"))) {
3396 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3397 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3398 activity = xmlnode_get_data(basicstatus);
3399 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3405 if (isonline) {
3406 gchar * status_id = NULL;
3407 if (activity) {
3408 if (strstr(activity, "busy")) {
3409 status_id = "busy";
3410 } else if (strstr(activity, "away")) {
3411 status_id = "away";
3415 if (!status_id) {
3416 status_id = "available";
3419 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3420 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3421 } else {
3422 purple_prpl_got_user_status(sip->account, uri, "offline", NULL);
3425 g_free(activity);
3426 xmlnode_free(pidf);
3429 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3431 const char *availability;
3432 const char *activity;
3433 const char *display_name = NULL;
3434 const char *activity_name;
3435 const char *name;
3436 char *uri;
3437 int avl;
3438 int act;
3439 struct sipe_buddy *sbuddy;
3441 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3443 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3444 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3445 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3446 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3447 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3448 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3449 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3450 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3451 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3452 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3453 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3454 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3456 name = xmlnode_get_attrib(xn_presentity, "uri");
3457 uri = g_strdup_printf("sip:%s", name);
3458 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3459 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3461 // updating display name if alias was just URI
3462 if (xn_display_name) {
3463 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3464 GSList *entry = buddies;
3465 PurpleBuddy *p_buddy;
3466 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3468 while (entry) {
3469 const char *email_str, *server_alias;
3471 p_buddy = entry->data;
3473 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3474 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3475 purple_blist_alias_buddy(p_buddy, display_name);
3478 server_alias = purple_buddy_get_server_alias(p_buddy);
3479 if (display_name &&
3480 ( (server_alias && strcmp(display_name, server_alias))
3481 || !server_alias || strlen(server_alias) == 0 )
3483 purple_blist_server_alias_buddy(p_buddy, display_name);
3486 if (email) {
3487 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3488 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3489 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3493 entry = entry->next;
3497 avl = atoi(availability);
3498 act = atoi(activity);
3500 if (act <= 100)
3501 activity_name = "away";
3502 else if (act <= 150)
3503 activity_name = "out-to-lunch";
3504 else if (act <= 300)
3505 activity_name = "be-right-back";
3506 else if (act <= 400)
3507 activity_name = "available";
3508 else if (act <= 500)
3509 activity_name = "on-the-phone";
3510 else if (act <= 600)
3511 activity_name = "busy";
3512 else
3513 activity_name = "available";
3515 if (avl == 0)
3516 activity_name = "offline";
3518 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3519 if (sbuddy)
3521 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3522 sbuddy->annotation = NULL;
3523 if (note) { sbuddy->annotation = g_strdup(note); }
3525 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3526 sbuddy->device_name = NULL;
3527 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3530 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3531 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3532 g_free(note);
3533 xmlnode_free(xn_presentity);
3534 g_free(uri);
3537 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3539 char *ctype = sipmsg_find_header(msg, "Content-Type");
3541 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3543 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3544 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3546 const char *content = msg->body;
3547 unsigned length = msg->bodylen;
3548 PurpleMimeDocument *mime = NULL;
3550 if (strstr(ctype, "multipart"))
3552 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3553 const char *content_type;
3554 GList* parts;
3555 mime = purple_mime_document_parse(doc);
3556 parts = purple_mime_document_get_parts(mime);
3557 while(parts) {
3558 content = purple_mime_part_get_data(parts->data);
3559 length = purple_mime_part_get_length(parts->data);
3560 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3561 if(content_type && strstr(content_type,"application/rlmi+xml"))
3563 process_incoming_notify_rlmi_resub(sip, content, length);
3565 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3567 process_incoming_notify_msrtc(sip, content, length);
3569 else
3571 process_incoming_notify_rlmi(sip, content, length);
3573 parts = parts->next;
3575 g_free(doc);
3577 if (mime)
3579 purple_mime_document_free(mime);
3582 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3584 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3586 else if(strstr(ctype, "application/rlmi+xml"))
3588 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3591 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3593 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3595 else
3597 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3602 * Dispatcher for all incoming subscription information
3603 * whether it comes from NOTIFY, BENOTIFY requests or
3604 * piggy-backed to subscription's OK responce.
3606 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3607 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3609 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3611 gchar *event = sipmsg_find_header(msg, "Event");
3612 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3613 const char *uri,*state;
3614 xmlnode *xn_list;
3615 xmlnode *xn_resource;
3616 xmlnode *xn_instance;
3618 int expires = 0;
3620 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3621 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3623 if (!request)
3625 const gchar *expires_header;
3626 expires_header = sipmsg_find_header(msg, "Expires");
3627 expires = expires_header ? strtol(expires_header, NULL, 10) : 0;
3628 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires);
3631 if (!subscription_state || strstr(subscription_state, "active"))
3633 if (event && !g_ascii_strcasecmp(event, "presence"))
3635 sipe_process_presence(sip, msg);
3637 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3639 sipe_process_roaming_contacts(sip, msg, NULL);
3641 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
3643 sipe_process_roaming_self(sip, msg);
3645 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
3647 //@TODO
3648 purple_debug_info("sipe", "vnd-microsoft-provisioning-v2 data is not supported yet.");
3650 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3652 sipe_process_roaming_acl(sip, msg);
3654 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3656 sipe_process_presence_wpending(sip, msg);
3658 else
3660 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3664 //The server sends a (BE)NOTIFY with the status 'terminated'
3665 if(request && subscription_state && strstr(subscription_state, "terminated") )
3667 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3668 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3669 g_free(from);
3672 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3673 if (request && !benotify)
3675 sipmsg_remove_header(msg, "Expires");
3676 sipmsg_remove_header(msg, "subscription-state");
3677 sipmsg_remove_header(msg, "Event");
3678 sipmsg_remove_header(msg, "Require");
3679 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3684 * unused. Needed?
3686 static gchar* gen_xpidf(struct sipe_account_data *sip)
3688 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3689 "<presence>\r\n"
3690 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3691 "<display name=\"sip:%s\"/>\r\n"
3692 "<atom id=\"1234\">\r\n"
3693 "<address uri=\"sip:%s\">\r\n"
3694 "<status status=\"%s\"/>\r\n"
3695 "</address>\r\n"
3696 "</atom>\r\n"
3697 "</presence>\r\n",
3698 sip->username,
3699 sip->username,
3700 sip->username,
3701 sip->status);
3702 return doc;
3707 static gchar* gen_pidf(struct sipe_account_data *sip)
3709 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3710 "<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"
3711 "<tuple id=\"0\">\r\n"
3712 "<status>\r\n"
3713 "<basic>open</basic>\r\n"
3714 "<ep:activities>\r\n"
3715 " <ep:activity>%s</ep:activity>\r\n"
3716 "</ep:activities>"
3717 "</status>\r\n"
3718 "</tuple>\r\n"
3719 "<ci:display-name>%s</ci:display-name>\r\n"
3720 "</presence>",
3721 sip->username,
3722 sip->status,
3723 sip->username);
3724 return doc;
3728 static gboolean
3729 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3731 if (msg->response == 488) {
3732 sip->presence_method_version = 1;
3733 send_presence_info(sip);
3735 return TRUE;
3738 static void send_presence_info_v0(struct sipe_account_data *sip, const char * note)
3740 int availability = 300; // online
3741 int activity = 400; // Available
3742 gchar *name;
3743 gchar *body;
3744 if (!strcmp(sip->status, "away")) {
3745 activity = 100;
3746 } else if (!strcmp(sip->status, "out-to-lunch")) {
3747 activity = 150;
3748 } else if (!strcmp(sip->status, "be-right-back")) {
3749 activity = 300;
3750 } else if (!strcmp(sip->status, "on-the-phone")) {
3751 activity = 500;
3752 } else if (!strcmp(sip->status, "do-not-disturb")) {
3753 activity = 600;
3754 } else if (!strcmp(sip->status, "busy")) {
3755 activity = 600;
3756 } else if (!strcmp(sip->status, "invisible")) {
3757 availability = 0; // offline
3758 activity = 100;
3761 name = g_strdup_printf("sip: sip:%s", sip->username);
3762 //@TODO: send user data - state; add hostname in upper case
3763 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3764 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
3765 g_free(name);
3766 g_free(body);
3769 static gboolean
3770 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3772 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3773 if (msg->response == 200) {
3774 sip->status_version = 0;
3775 send_presence_info(sip);
3777 return TRUE;
3780 static gboolean
3781 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3783 if (msg->response == 409) {
3784 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3785 // TODO need to parse the version #'s?
3786 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3787 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3788 gchar *tmp;
3789 gchar *hdr;
3791 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg->body);
3793 tmp = get_contact(sip);
3794 hdr = g_strdup_printf("Contact: %s\r\n"
3795 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3797 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3799 g_free(tmp);
3800 g_free(hdr);
3801 g_free(uri);
3802 g_free(doc);
3804 return TRUE;
3807 static void send_presence_info_v1(struct sipe_account_data *sip, const char * note)
3809 int code;
3810 gchar *uri;
3811 gchar *doc;
3812 gchar *tmp;
3813 gchar *hdr;
3814 if (!strcmp(sip->status, "away")) {
3815 code = 12000;
3816 } else if (!strcmp(sip->status, "busy")) {
3817 code = 6000;
3818 } else {
3819 // Available
3820 code = 3000;
3823 uri = g_strdup_printf("sip:%s", sip->username);
3824 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3825 sip->status_version, code,
3826 sip->status_version, code,
3827 sip->status_version, note ? note : "",
3828 sip->status_version, note ? note : "",
3829 sip->status_version, note ? note : ""
3831 sip->status_version++;
3833 tmp = get_contact(sip);
3834 hdr = g_strdup_printf("Contact: %s\r\n"
3835 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3837 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
3839 g_free(tmp);
3840 g_free(hdr);
3841 g_free(uri);
3842 g_free(doc);
3845 static void send_presence_info(struct sipe_account_data *sip)
3847 PurpleStatus * status = purple_account_get_active_status(sip->account);
3848 const gchar *note;
3849 if (!status) return;
3851 note = purple_status_get_attr_string(status, "message");
3853 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
3854 if (sip->presence_method_version != 1) {
3855 send_presence_info_v0(sip, note);
3856 } else {
3857 send_presence_info_v1(sip, note);
3861 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3863 gboolean found = FALSE;
3864 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3865 if (msg->response == 0) { /* request */
3866 if (!strcmp(msg->method, "MESSAGE")) {
3867 process_incoming_message(sip, msg);
3868 found = TRUE;
3869 } else if (!strcmp(msg->method, "NOTIFY")) {
3870 purple_debug_info("sipe","send->process_incoming_notify\n");
3871 process_incoming_notify(sip, msg, TRUE, FALSE);
3872 found = TRUE;
3873 } else if (!strcmp(msg->method, "BENOTIFY")) {
3874 purple_debug_info("sipe","send->process_incoming_benotify\n");
3875 process_incoming_notify(sip, msg, TRUE, TRUE);
3876 found = TRUE;
3877 } else if (!strcmp(msg->method, "INVITE")) {
3878 process_incoming_invite(sip, msg);
3879 found = TRUE;
3880 } else if (!strcmp(msg->method, "OPTIONS")) {
3881 process_incoming_options(sip, msg);
3882 found = TRUE;
3883 } else if (!strcmp(msg->method, "INFO")) {
3884 // TODO needs work
3885 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3886 if (from) {
3887 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3889 g_free(from);
3890 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3891 found = TRUE;
3892 } else if (!strcmp(msg->method, "ACK")) {
3893 // ACK's don't need any response
3894 found = TRUE;
3895 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3896 // LCS 2005 sends us these - just respond 200 OK
3897 found = TRUE;
3898 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3899 } else if (!strcmp(msg->method, "BYE")) {
3900 struct sip_im_session *session;
3901 gchar *from;
3902 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3904 from = parse_from(sipmsg_find_header(msg, "From"));
3905 session = find_im_session (sip, from);
3906 g_free(from);
3908 if (session) {
3909 // TODO Let the user know the other user left the conversation?
3910 im_session_destroy(sip, session);
3913 found = TRUE;
3914 } else {
3915 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3917 } else { /* response */
3918 struct transaction *trans = transactions_find(sip, msg);
3919 if (trans) {
3920 if (msg->response == 407) {
3921 gchar *resend, *auth, *ptmp;
3923 if (sip->proxy.retries > 30) return;
3924 sip->proxy.retries++;
3925 /* do proxy authentication */
3927 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3929 fill_auth(sip, ptmp, &sip->proxy);
3930 auth = auth_header(sip, &sip->proxy, trans->msg);
3931 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3932 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
3933 g_free(auth);
3934 resend = sipmsg_to_string(trans->msg);
3935 /* resend request */
3936 sendout_pkt(sip->gc, resend);
3937 g_free(resend);
3938 } else {
3939 if (msg->response == 100 || msg->response == 180) {
3940 /* ignore provisional response */
3941 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
3942 } else {
3943 sip->proxy.retries = 0;
3944 if (!strcmp(trans->msg->method, "REGISTER")) {
3945 if (msg->response == 401)
3947 sip->registrar.retries++;
3948 sip->registrar.expires = 0;
3950 else
3952 sip->registrar.retries = 0;
3954 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
3955 } else {
3956 if (msg->response == 401) {
3957 gchar *resend, *auth, *ptmp;
3959 if (sip->registrar.retries > 4) return;
3960 sip->registrar.retries++;
3962 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3963 ptmp = sipmsg_find_auth_header(msg, "NTLM");
3964 } else {
3965 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
3968 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
3970 fill_auth(sip, ptmp, &sip->registrar);
3971 auth = auth_header(sip, &sip->registrar, trans->msg);
3972 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3973 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
3975 //sipmsg_remove_header(trans->msg, "Authorization");
3976 //sipmsg_add_header(trans->msg, "Authorization", auth);
3977 g_free(auth);
3978 resend = sipmsg_to_string(trans->msg);
3979 /* resend request */
3980 sendout_pkt(sip->gc, resend);
3981 g_free(resend);
3985 if (trans->callback) {
3986 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
3987 /* call the callback to process response*/
3988 (trans->callback)(sip, msg, trans);
3990 /* Not sure if this is needed or what needs to be done
3991 but transactions seem to be removed prematurely so
3992 this only removes them if the response is 200 OK */
3993 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
3994 /*Has a bug and it's unneccesary*/
3995 /*transactions_remove(sip, trans);*/
3999 found = TRUE;
4000 } else {
4001 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
4004 if (!found) {
4005 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4009 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4011 char *cur;
4012 char *dummy;
4013 struct sipmsg *msg;
4014 int restlen;
4015 cur = conn->inbuf;
4017 /* according to the RFC remove CRLF at the beginning */
4018 while (*cur == '\r' || *cur == '\n') {
4019 cur++;
4021 if (cur != conn->inbuf) {
4022 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4023 conn->inbufused = strlen(conn->inbuf);
4026 /* Received a full Header? */
4027 sip->processing_input = TRUE;
4028 while (sip->processing_input &&
4029 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4030 time_t currtime = time(NULL);
4031 cur += 2;
4032 cur[0] = '\0';
4033 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4034 msg = sipmsg_parse_header(conn->inbuf);
4035 cur[0] = '\r';
4036 cur += 2;
4037 restlen = conn->inbufused - (cur - conn->inbuf);
4038 if (restlen >= msg->bodylen) {
4039 dummy = g_malloc(msg->bodylen + 1);
4040 memcpy(dummy, cur, msg->bodylen);
4041 dummy[msg->bodylen] = '\0';
4042 msg->body = dummy;
4043 cur += msg->bodylen;
4044 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4045 conn->inbufused = strlen(conn->inbuf);
4046 } else {
4047 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4048 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4049 sipmsg_free(msg);
4050 return;
4053 /*if (msg->body) {
4054 purple_debug_info("sipe", "body:\n%s", msg->body);
4057 // Verify the signature before processing it
4058 if (sip->registrar.ntlm_key) {
4059 struct sipmsg_breakdown msgbd;
4060 gchar *signature_input_str;
4061 gchar *signature = NULL;
4062 gchar *rspauth;
4063 msgbd.msg = msg;
4064 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4065 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4066 if (signature_input_str != NULL) {
4067 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4069 g_free(signature_input_str);
4071 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4073 if (signature != NULL) {
4074 if (rspauth != NULL) {
4075 if (purple_ntlm_verify_signature (signature, rspauth)) {
4076 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4077 process_input_message(sip, msg);
4078 } else {
4079 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4080 purple_connection_error(sip->gc, _("Invalid message signature received"));
4081 sip->gc->wants_to_die = TRUE;
4083 } else if (msg->response == 401) {
4084 purple_connection_error(sip->gc, _("Wrong Password"));
4085 sip->gc->wants_to_die = TRUE;
4087 g_free(signature);
4090 g_free(rspauth);
4091 sipmsg_breakdown_free(&msgbd);
4092 } else {
4093 process_input_message(sip, msg);
4096 sipmsg_free(msg);
4100 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4102 PurpleConnection *gc = data;
4103 struct sipe_account_data *sip = gc->proto_data;
4104 struct sipmsg *msg;
4105 int len;
4106 time_t currtime;
4108 static char buffer[65536];
4109 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4110 buffer[len] = '\0';
4111 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4112 msg = sipmsg_parse_msg(buffer);
4113 if (msg) process_input_message(sip, msg);
4117 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4119 struct sipe_account_data *sip = gc->proto_data;
4120 PurpleSslConnection *gsc = sip->gsc;
4122 purple_debug_error("sipe", "%s",debug);
4123 purple_connection_error(gc, msg);
4125 /* Invalidate this connection. Next send will open a new one */
4126 if (gsc) {
4127 connection_remove(sip, gsc->fd);
4128 purple_ssl_close(gsc);
4130 sip->gsc = NULL;
4131 sip->fd = -1;
4134 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4136 PurpleConnection *gc = data;
4137 struct sipe_account_data *sip;
4138 struct sip_connection *conn;
4139 int readlen, len;
4140 gboolean firstread = TRUE;
4142 /* NOTE: This check *IS* necessary */
4143 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4144 purple_ssl_close(gsc);
4145 return;
4148 sip = gc->proto_data;
4149 conn = connection_find(sip, gsc->fd);
4150 if (conn == NULL) {
4151 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4152 gc->wants_to_die = TRUE;
4153 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4154 return;
4157 /* Read all available data from the SSL connection */
4158 do {
4159 /* Increase input buffer size as needed */
4160 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4161 conn->inbuflen += SIMPLE_BUF_INC;
4162 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4163 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4166 /* Try to read as much as there is space left in the buffer */
4167 readlen = conn->inbuflen - conn->inbufused - 1;
4168 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4170 if (len < 0 && errno == EAGAIN) {
4171 /* Try again later */
4172 return;
4173 } else if (len < 0) {
4174 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4175 return;
4176 } else if (firstread && (len == 0)) {
4177 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4178 return;
4181 conn->inbufused += len;
4182 firstread = FALSE;
4184 /* Equivalence indicates that there is possibly more data to read */
4185 } while (len == readlen);
4187 conn->inbuf[conn->inbufused] = '\0';
4188 process_input(sip, conn);
4192 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4194 PurpleConnection *gc = data;
4195 struct sipe_account_data *sip = gc->proto_data;
4196 int len;
4197 struct sip_connection *conn = connection_find(sip, source);
4198 if (!conn) {
4199 purple_debug_error("sipe", "Connection not found!\n");
4200 return;
4203 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4204 conn->inbuflen += SIMPLE_BUF_INC;
4205 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4208 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4210 if (len < 0 && errno == EAGAIN)
4211 return;
4212 else if (len <= 0) {
4213 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4214 connection_remove(sip, source);
4215 if (sip->fd == source) sip->fd = -1;
4216 return;
4219 conn->inbufused += len;
4220 conn->inbuf[conn->inbufused] = '\0';
4222 process_input(sip, conn);
4225 /* Callback for new connections on incoming TCP port */
4226 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4228 PurpleConnection *gc = data;
4229 struct sipe_account_data *sip = gc->proto_data;
4230 struct sip_connection *conn;
4232 int newfd = accept(source, NULL, NULL);
4234 conn = connection_create(sip, newfd);
4236 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4239 static void login_cb(gpointer data, gint source, const gchar *error_message)
4241 PurpleConnection *gc = data;
4242 struct sipe_account_data *sip;
4243 struct sip_connection *conn;
4245 if (!PURPLE_CONNECTION_IS_VALID(gc))
4247 if (source >= 0)
4248 close(source);
4249 return;
4252 if (source < 0) {
4253 purple_connection_error(gc, _("Could not connect"));
4254 return;
4257 sip = gc->proto_data;
4258 sip->fd = source;
4259 sip->last_keepalive = time(NULL);
4261 conn = connection_create(sip, source);
4263 do_register(sip);
4265 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4268 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4270 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4271 if (sip == NULL) return;
4273 do_register(sip);
4276 static guint sipe_ht_hash_nick(const char *nick)
4278 char *lc = g_utf8_strdown(nick, -1);
4279 guint bucket = g_str_hash(lc);
4280 g_free(lc);
4282 return bucket;
4285 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4287 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4290 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4292 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4294 sip->listen_data = NULL;
4296 if (listenfd == -1) {
4297 purple_connection_error(sip->gc, _("Could not create listen socket"));
4298 return;
4301 sip->fd = listenfd;
4303 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4304 sip->listenfd = sip->fd;
4306 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4308 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4309 do_register(sip);
4312 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4314 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4315 int addr_size;
4317 sip->query_data = NULL;
4319 if (!hosts || !hosts->data) {
4320 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4321 return;
4324 addr_size = GPOINTER_TO_INT(hosts->data);
4325 hosts = g_slist_remove(hosts, hosts->data);
4326 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4327 g_free(hosts->data);
4328 hosts = g_slist_remove(hosts, hosts->data);
4329 while (hosts) {
4330 hosts = g_slist_remove(hosts, hosts->data);
4331 g_free(hosts->data);
4332 hosts = g_slist_remove(hosts, hosts->data);
4335 /* create socket for incoming connections */
4336 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4337 sipe_udp_host_resolved_listen_cb, sip);
4338 if (sip->listen_data == NULL) {
4339 purple_connection_error(sip->gc, _("Could not create listen socket"));
4340 return;
4344 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4345 gpointer data)
4347 PurpleConnection *gc = data;
4348 struct sipe_account_data *sip;
4350 /* If the connection is already disconnected, we don't need to do anything else */
4351 if (!PURPLE_CONNECTION_IS_VALID(gc))
4352 return;
4354 sip = gc->proto_data;
4355 sip->fd = -1;
4356 sip->gsc = NULL;
4358 switch(error) {
4359 case PURPLE_SSL_CONNECT_FAILED:
4360 purple_connection_error(gc, _("Connection Failed"));
4361 break;
4362 case PURPLE_SSL_HANDSHAKE_FAILED:
4363 purple_connection_error(gc, _("SSL Handshake Failed"));
4364 break;
4365 case PURPLE_SSL_CERTIFICATE_INVALID:
4366 purple_connection_error(gc, _("SSL Certificate Invalid"));
4367 break;
4371 static void
4372 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4374 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4375 PurpleProxyConnectData *connect_data;
4377 sip->listen_data = NULL;
4379 sip->listenfd = listenfd;
4380 if (sip->listenfd == -1) {
4381 purple_connection_error(sip->gc, _("Could not create listen socket"));
4382 return;
4385 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4386 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4387 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4388 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4389 sipe_newconn_cb, sip->gc);
4390 purple_debug_info("sipe", "connecting to %s port %d\n",
4391 sip->realhostname, sip->realport);
4392 /* open tcp connection to the server */
4393 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4394 sip->realport, login_cb, sip->gc);
4396 if (connect_data == NULL) {
4397 purple_connection_error(sip->gc, _("Couldn't create socket"));
4402 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4404 PurpleAccount *account = sip->account;
4405 PurpleConnection *gc = sip->gc;
4407 if (purple_account_get_bool(account, "useport", FALSE)) {
4408 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4409 port = purple_account_get_int(account, "port", 0);
4410 } else {
4411 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4414 sip->realhostname = hostname;
4415 sip->realport = port;
4417 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4418 hostname, port);
4420 /* TODO: is there a good default grow size? */
4421 if (sip->transport != SIPE_TRANSPORT_UDP)
4422 sip->txbuf = purple_circ_buffer_new(0);
4424 if (sip->transport == SIPE_TRANSPORT_TLS) {
4425 /* SSL case */
4426 if (!purple_ssl_is_supported()) {
4427 gc->wants_to_die = TRUE;
4428 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4429 return;
4432 purple_debug_info("sipe", "using SSL\n");
4434 sip->gsc = purple_ssl_connect(account, hostname, port,
4435 login_cb_ssl, sipe_ssl_connect_failure, gc);
4436 if (sip->gsc == NULL) {
4437 purple_connection_error(gc, _("Could not create SSL context"));
4438 return;
4440 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4441 /* UDP case */
4442 purple_debug_info("sipe", "using UDP\n");
4444 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4445 if (sip->query_data == NULL) {
4446 purple_connection_error(gc, _("Could not resolve hostname"));
4448 } else {
4449 /* TCP case */
4450 purple_debug_info("sipe", "using TCP\n");
4451 /* create socket for incoming connections */
4452 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4453 sipe_tcp_connect_listen_cb, sip);
4454 if (sip->listen_data == NULL) {
4455 purple_connection_error(gc, _("Could not create listen socket"));
4456 return;
4461 /* Service list for autodection */
4462 static const struct sipe_service_data service_autodetect[] = {
4463 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4464 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4465 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4466 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4467 { NULL, NULL, 0 }
4470 /* Service list for SSL/TLS */
4471 static const struct sipe_service_data service_tls[] = {
4472 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4473 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4474 { NULL, NULL, 0 }
4477 /* Service list for TCP */
4478 static const struct sipe_service_data service_tcp[] = {
4479 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4480 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4481 { NULL, NULL, 0 }
4484 /* Service list for UDP */
4485 static const struct sipe_service_data service_udp[] = {
4486 { "sip", "udp", SIPE_TRANSPORT_UDP },
4487 { NULL, NULL, 0 }
4490 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4491 static void resolve_next_service(struct sipe_account_data *sip,
4492 const struct sipe_service_data *start)
4494 if (start) {
4495 sip->service_data = start;
4496 } else {
4497 sip->service_data++;
4498 if (sip->service_data->service == NULL) {
4499 gchar *hostname;
4500 /* Try connecting to the SIP hostname directly */
4501 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4502 if (sip->auto_transport) {
4503 // If SSL is supported, default to using it; OCS servers aren't configured
4504 // by default to accept TCP
4505 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4506 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4507 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4510 hostname = g_strdup(sip->sipdomain);
4511 create_connection(sip, hostname, 0);
4512 return;
4516 /* Try to resolve next service */
4517 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4518 sip->service_data->transport,
4519 sip->sipdomain,
4520 srvresolved, sip);
4523 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4525 struct sipe_account_data *sip = data;
4527 sip->srv_query_data = NULL;
4529 /* find the host to connect to */
4530 if (results) {
4531 gchar *hostname = g_strdup(resp->hostname);
4532 int port = resp->port;
4533 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4534 hostname, port);
4535 g_free(resp);
4537 sip->transport = sip->service_data->type;
4539 create_connection(sip, hostname, port);
4540 } else {
4541 resolve_next_service(sip, NULL);
4545 static void sipe_login(PurpleAccount *account)
4547 PurpleConnection *gc;
4548 struct sipe_account_data *sip;
4549 gchar **signinname_login, **userserver, **domain_user;
4550 const char *transport;
4552 const char *username = purple_account_get_username(account);
4553 gc = purple_account_get_connection(account);
4555 if (strpbrk(username, " \t\v\r\n") != NULL) {
4556 gc->wants_to_die = TRUE;
4557 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4558 return;
4561 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4562 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4563 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4564 sip->gc = gc;
4565 sip->account = account;
4566 sip->registerexpire = 900;
4567 sip->reregister_set = FALSE;
4568 sip->reauthenticate_set = FALSE;
4569 sip->subscribed = FALSE;
4570 sip->subscribed_buddies = FALSE;
4572 signinname_login = g_strsplit(username, ",", 2);
4574 userserver = g_strsplit(signinname_login[0], "@", 2);
4575 purple_connection_set_display_name(gc, userserver[0]);
4576 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4577 sip->sipdomain = g_strdup(userserver[1]);
4579 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4580 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4581 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4583 sip->password = g_strdup(purple_connection_get_password(gc));
4585 g_strfreev(userserver);
4586 g_strfreev(domain_user);
4587 g_strfreev(signinname_login);
4589 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4591 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4593 /* TODO: Set the status correctly. */
4594 sip->status = g_strdup("available");
4596 transport = purple_account_get_string(account, "transport", "auto");
4597 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4598 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4599 SIPE_TRANSPORT_UDP;
4601 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4602 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4603 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4604 } else if (strcmp(transport, "auto") == 0) {
4605 sip->auto_transport = TRUE;
4606 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4607 } else if (strcmp(transport, "tls") == 0) {
4608 resolve_next_service(sip, service_tls);
4609 } else if (strcmp(transport, "tcp") == 0) {
4610 resolve_next_service(sip, service_tcp);
4611 } else {
4612 resolve_next_service(sip, service_udp);
4616 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4618 connection_free_all(sip);
4620 if (sip->query_data != NULL)
4621 purple_dnsquery_destroy(sip->query_data);
4622 sip->query_data = NULL;
4624 if (sip->srv_query_data != NULL)
4625 purple_srv_cancel(sip->srv_query_data);
4626 sip->srv_query_data = NULL;
4628 if (sip->listen_data != NULL)
4629 purple_network_listen_cancel(sip->listen_data);
4630 sip->listen_data = NULL;
4632 if (sip->gsc != NULL)
4633 purple_ssl_close(sip->gsc);
4634 sip->gsc = NULL;
4636 sipe_auth_free(&sip->registrar);
4637 sipe_auth_free(&sip->proxy);
4639 if (sip->txbuf)
4640 purple_circ_buffer_destroy(sip->txbuf);
4641 sip->txbuf = NULL;
4643 g_free(sip->realhostname);
4644 sip->realhostname = NULL;
4646 if (sip->listenpa)
4647 purple_input_remove(sip->listenpa);
4648 sip->listenpa = 0;
4649 if (sip->tx_handler)
4650 purple_input_remove(sip->tx_handler);
4651 sip->tx_handler = 0;
4652 if (sip->resendtimeout)
4653 purple_timeout_remove(sip->resendtimeout);
4654 sip->resendtimeout = 0;
4655 if (sip->timeouts) {
4656 GSList *entry = sip->timeouts;
4657 while (entry) {
4658 struct scheduled_action *sched_action = entry->data;
4659 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4660 purple_timeout_remove(sched_action->timeout_handler);
4661 g_free(sched_action->payload);
4662 g_free(sched_action->name);
4663 g_free(sched_action);
4664 entry = entry->next;
4667 g_slist_free(sip->timeouts);
4669 g_slist_free(sip->allow_events);
4671 if (sip->contact)
4672 g_free(sip->contact);
4673 sip->contact = NULL;
4674 if (sip->regcallid)
4675 g_free(sip->regcallid);
4676 sip->regcallid = NULL;
4678 sip->fd = -1;
4679 sip->processing_input = FALSE;
4683 * A callback for g_hash_table_foreach_remove
4685 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4687 sipe_free_buddy(buddy);
4690 static void sipe_close(PurpleConnection *gc)
4692 struct sipe_account_data *sip = gc->proto_data;
4694 if (sip) {
4695 /* leave all conversations */
4696 im_session_close_all(sip);
4698 /* unregister */
4699 do_register_exp(sip, 0);
4701 sipe_connection_cleanup(sip);
4702 g_free(sip->sipdomain);
4703 g_free(sip->username);
4704 g_free(sip->password);
4705 g_free(sip->authdomain);
4706 g_free(sip->authuser);
4707 g_free(sip->status);
4709 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4710 g_hash_table_destroy(sip->buddies);
4712 g_free(gc->proto_data);
4713 gc->proto_data = NULL;
4716 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4718 PurpleAccount *acct = purple_connection_get_account(gc);
4719 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4720 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4721 if (conv == NULL)
4722 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4723 purple_conversation_present(conv);
4724 g_free(id);
4727 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4730 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4731 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4734 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4736 PurpleNotifySearchResults *results;
4737 PurpleNotifySearchColumn *column;
4738 xmlnode *searchResults;
4739 xmlnode *mrow;
4740 int match_count = 0;
4741 gboolean more = FALSE;
4742 gchar *secondary;
4744 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
4746 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4747 if (!searchResults) {
4748 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4749 return FALSE;
4752 results = purple_notify_searchresults_new();
4754 if (results == NULL) {
4755 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4756 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4758 xmlnode_free(searchResults);
4759 return FALSE;
4762 column = purple_notify_searchresults_column_new(_("User Name"));
4763 purple_notify_searchresults_column_add(results, column);
4765 column = purple_notify_searchresults_column_new(_("Name"));
4766 purple_notify_searchresults_column_add(results, column);
4768 column = purple_notify_searchresults_column_new(_("Company"));
4769 purple_notify_searchresults_column_add(results, column);
4771 column = purple_notify_searchresults_column_new(_("Country"));
4772 purple_notify_searchresults_column_add(results, column);
4774 column = purple_notify_searchresults_column_new(_("Email"));
4775 purple_notify_searchresults_column_add(results, column);
4777 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4778 GList *row = NULL;
4780 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4781 row = g_list_append(row, g_strdup(uri_parts[1]));
4782 g_strfreev(uri_parts);
4784 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4785 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4786 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4787 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4789 purple_notify_searchresults_row_add(results, row);
4790 match_count++;
4793 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4794 char *data = xmlnode_get_data_unescaped(mrow);
4795 more = (g_strcasecmp(data, "true") == 0);
4796 g_free(data);
4799 secondary = g_strdup_printf(
4800 dngettext(GETTEXT_PACKAGE,
4801 "Found %d contact%s:",
4802 "Found %d contacts%s:", match_count),
4803 match_count, more ? _(" (more matched your query)") : "");
4805 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4806 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4807 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4809 g_free(secondary);
4810 xmlnode_free(searchResults);
4811 return TRUE;
4814 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4816 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4817 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4818 unsigned i = 0;
4820 do {
4821 PurpleRequestField *field = entries->data;
4822 const char *id = purple_request_field_get_id(field);
4823 const char *value = purple_request_field_string_get_value(field);
4825 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
4827 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
4828 } while ((entries = g_list_next(entries)) != NULL);
4829 attrs[i] = NULL;
4831 if (i > 0) {
4832 gchar *query = g_strjoinv(NULL, attrs);
4833 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
4834 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
4835 send_soap_request_with_cb(gc->proto_data, body,
4836 (TransCallback) process_search_contact_response, NULL);
4837 g_free(body);
4838 g_free(query);
4841 g_strfreev(attrs);
4844 static void sipe_show_find_contact(PurplePluginAction *action)
4846 PurpleConnection *gc = (PurpleConnection *) action->context;
4847 PurpleRequestFields *fields;
4848 PurpleRequestFieldGroup *group;
4849 PurpleRequestField *field;
4851 fields = purple_request_fields_new();
4852 group = purple_request_field_group_new(NULL);
4853 purple_request_fields_add_group(fields, group);
4855 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4856 purple_request_field_group_add_field(group, field);
4857 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4858 purple_request_field_group_add_field(group, field);
4859 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4860 purple_request_field_group_add_field(group, field);
4861 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4862 purple_request_field_group_add_field(group, field);
4864 purple_request_fields(gc,
4865 _("Search"),
4866 _("Search for a Contact"),
4867 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4868 fields,
4869 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4870 _("_Cancel"), NULL,
4871 purple_connection_get_account(gc), NULL, NULL, gc);
4874 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4876 GList *menu = NULL;
4877 PurplePluginAction *act;
4879 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4880 menu = g_list_prepend(menu, act);
4882 menu = g_list_reverse(menu);
4884 return menu;
4887 static void dummy_permit_deny(PurpleConnection *gc)
4891 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4893 return TRUE;
4897 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4899 return TRUE;
4903 static char *sipe_status_text(PurpleBuddy *buddy)
4905 struct sipe_account_data *sip;
4906 struct sipe_buddy *sbuddy;
4907 char *text = NULL;
4909 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4910 if (sip) //happens on pidgin exit
4912 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4913 if (sbuddy && sbuddy->annotation)
4915 text = g_strdup(sbuddy->annotation);
4919 return text;
4922 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4924 const PurplePresence *presence = purple_buddy_get_presence(buddy);
4925 const PurpleStatus *status = purple_presence_get_active_status(presence);
4926 struct sipe_account_data *sip;
4927 struct sipe_buddy *sbuddy;
4928 char *annotation = NULL;
4930 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4931 if (sip) //happens on pidgin exit
4933 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4934 if (sbuddy)
4936 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
4940 //Layout
4941 if (purple_presence_is_online(presence))
4943 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
4946 if (annotation)
4948 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
4949 g_free(annotation);
4954 static GHashTable *
4955 sipe_get_account_text_table(PurpleAccount *account)
4957 GHashTable *table;
4958 table = g_hash_table_new(g_str_hash, g_str_equal);
4959 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
4960 return table;
4963 static PurpleBuddy *
4964 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
4966 PurpleBuddy *clone;
4967 const gchar *server_alias, *email;
4968 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
4970 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
4972 purple_blist_add_buddy(clone, NULL, group, NULL);
4974 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
4975 if (server_alias) {
4976 purple_blist_server_alias_buddy(clone, server_alias);
4979 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4980 if (email) {
4981 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
4984 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
4985 //for UI to update;
4986 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
4987 return clone;
4990 static void
4991 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
4993 PurpleBuddy *buddy, *b;
4994 PurpleConnection *gc;
4995 PurpleGroup * group = purple_find_group(group_name);
4997 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
4999 buddy = (PurpleBuddy *)node;
5001 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5002 gc = purple_account_get_connection(buddy->account);
5004 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5005 if (!b){
5006 b = purple_blist_add_buddy_clone(group, buddy);
5009 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5012 static void
5013 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5015 const gchar *email;
5016 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5018 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5019 if (email)
5021 char *mailto = g_strdup_printf("mailto:%s", email);
5022 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5023 #ifndef _WIN32
5025 pid_t pid;
5026 char *const parmList[] = {mailto, NULL};
5027 if ((pid = fork()) == -1)
5029 purple_debug_info("sipe", "fork() error\n");
5031 else if (pid == 0)
5033 execvp("xdg-email", parmList);
5034 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5037 #else
5039 BOOL ret;
5040 _flushall();
5041 errno = 0;
5042 //@TODO resolve env variable %WINDIR% first
5043 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5044 if (errno)
5046 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5049 #endif
5051 g_free(mailto);
5053 else
5055 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5060 * A menu which appear when right-clicking on buddy in contact list.
5062 static GList *
5063 sipe_buddy_menu(PurpleBuddy *buddy)
5065 PurpleBlistNode *g_node;
5066 PurpleGroup *group, *gr_parent;
5067 PurpleMenuAction *act;
5068 GList *menu = NULL;
5069 GList *menu_groups = NULL;
5071 act = purple_menu_action_new(_("Send Email..."),
5072 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5073 NULL, NULL);
5074 menu = g_list_prepend(menu, act);
5076 gr_parent = purple_buddy_get_group(buddy);
5077 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5078 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5079 continue;
5081 group = (PurpleGroup *)g_node;
5082 if (group == gr_parent)
5083 continue;
5085 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5086 continue;
5088 act = purple_menu_action_new(purple_group_get_name(group),
5089 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5090 group->name, NULL);
5091 menu_groups = g_list_prepend(menu_groups, act);
5093 menu_groups = g_list_reverse(menu_groups);
5095 act = purple_menu_action_new(_("Copy to"),
5096 NULL,
5097 NULL, menu_groups);
5098 menu = g_list_prepend(menu, act);
5099 menu = g_list_reverse(menu);
5101 return menu;
5104 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5105 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5106 return sipe_buddy_menu((PurpleBuddy *) node);
5107 } else {
5108 return NULL;
5112 static gboolean
5113 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5115 gboolean ret = TRUE;
5116 char *username = (char *)trans->payload;
5118 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5119 PurpleBuddy *pbuddy;
5120 struct sipe_buddy *sbuddy;
5121 const char *alias;
5122 char *server_alias = NULL;
5123 char *email = NULL;
5124 const char *device_name = NULL;
5126 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5128 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5129 alias = purple_buddy_get_local_alias(pbuddy);
5131 if (sip)
5133 //will query buddy UA's capabilities and send answer to log
5134 sipe_options_request(sip, username);
5136 sbuddy = g_hash_table_lookup(sip->buddies, username);
5137 if (sbuddy)
5139 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5143 if (msg->response != 200) {
5144 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5145 } else {
5146 xmlnode *searchResults;
5147 xmlnode *mrow;
5149 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5150 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5151 if (!searchResults) {
5152 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5153 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5154 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5155 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5156 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5157 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5158 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5159 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5160 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5161 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5162 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5163 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5164 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5165 if (!email || strcmp("", email)) {
5166 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5167 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5171 xmlnode_free(searchResults);
5174 purple_notify_user_info_add_section_break(info);
5176 if (!server_alias || !strcmp("", server_alias)) {
5177 g_free(server_alias);
5178 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5179 if (server_alias) {
5180 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5184 // same as server alias, do not present
5185 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5186 if (alias)
5188 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5191 if (!email || !strcmp("", email)) {
5192 g_free(email);
5193 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5194 if (email) {
5195 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5199 if (device_name)
5201 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5204 /* show a buddy's user info in a nice dialog box */
5205 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5206 username, /* buddy's username */
5207 info, /* body */
5208 NULL, /* callback called when dialog closed */
5209 NULL); /* userdata for callback */
5211 return ret;
5215 * AD search first, LDAP based
5217 static void sipe_get_info(PurpleConnection *gc, const char *username)
5219 char *row = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5220 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5222 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5223 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5224 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5225 g_free(body);
5226 g_free(row);
5229 static PurplePlugin *my_protocol = NULL;
5231 static PurplePluginProtocolInfo prpl_info =
5234 NULL, /* user_splits */
5235 NULL, /* protocol_options */
5236 NO_BUDDY_ICONS, /* icon_spec */
5237 sipe_list_icon, /* list_icon */
5238 NULL, /* list_emblems */
5239 sipe_status_text, /* status_text */
5240 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5241 sipe_status_types, /* away_states */
5242 sipe_blist_node_menu, /* blist_node_menu */
5243 NULL, /* chat_info */
5244 NULL, /* chat_info_defaults */
5245 sipe_login, /* login */
5246 sipe_close, /* close */
5247 sipe_im_send, /* send_im */
5248 NULL, /* set_info */ // TODO maybe
5249 sipe_send_typing, /* send_typing */
5250 sipe_get_info, /* get_info */
5251 sipe_set_status, /* set_status */
5252 NULL, /* set_idle */
5253 NULL, /* change_passwd */
5254 sipe_add_buddy, /* add_buddy */
5255 NULL, /* add_buddies */
5256 sipe_remove_buddy, /* remove_buddy */
5257 NULL, /* remove_buddies */
5258 sipe_add_permit, /* add_permit */
5259 sipe_add_deny, /* add_deny */
5260 sipe_add_deny, /* rem_permit */
5261 sipe_add_permit, /* rem_deny */
5262 dummy_permit_deny, /* set_permit_deny */
5263 NULL, /* join_chat */
5264 NULL, /* reject_chat */
5265 NULL, /* get_chat_name */
5266 NULL, /* chat_invite */
5267 NULL, /* chat_leave */
5268 NULL, /* chat_whisper */
5269 NULL, /* chat_send */
5270 sipe_keep_alive, /* keepalive */
5271 NULL, /* register_user */
5272 NULL, /* get_cb_info */ // deprecated
5273 NULL, /* get_cb_away */ // deprecated
5274 sipe_alias_buddy, /* alias_buddy */
5275 sipe_group_buddy, /* group_buddy */
5276 sipe_rename_group, /* rename_group */
5277 NULL, /* buddy_free */
5278 sipe_convo_closed, /* convo_closed */
5279 purple_normalize_nocase, /* normalize */
5280 NULL, /* set_buddy_icon */
5281 sipe_remove_group, /* remove_group */
5282 NULL, /* get_cb_real_name */ // TODO?
5283 NULL, /* set_chat_topic */
5284 NULL, /* find_blist_chat */
5285 NULL, /* roomlist_get_list */
5286 NULL, /* roomlist_cancel */
5287 NULL, /* roomlist_expand_category */
5288 NULL, /* can_receive_file */
5289 NULL, /* send_file */
5290 NULL, /* new_xfer */
5291 NULL, /* offline_message */
5292 NULL, /* whiteboard_prpl_ops */
5293 sipe_send_raw, /* send_raw */
5294 NULL, /* roomlist_room_serialize */
5295 NULL, /* unregister_user */
5296 NULL, /* send_attention */
5297 NULL, /* get_attention_types */
5299 sizeof(PurplePluginProtocolInfo), /* struct_size */
5300 sipe_get_account_text_table, /* get_account_text_table */
5304 static PurplePluginInfo info = {
5305 PURPLE_PLUGIN_MAGIC,
5306 PURPLE_MAJOR_VERSION,
5307 PURPLE_MINOR_VERSION,
5308 PURPLE_PLUGIN_PROTOCOL, /**< type */
5309 NULL, /**< ui_requirement */
5310 0, /**< flags */
5311 NULL, /**< dependencies */
5312 PURPLE_PRIORITY_DEFAULT, /**< priority */
5313 "prpl-sipe", /**< id */
5314 "Microsoft LCS/OCS", /**< name */
5315 VERSION, /**< version */
5316 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5317 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5318 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5319 "Gabriel Burt <gburt@novell.com>", /**< author */
5320 PURPLE_WEBSITE, /**< homepage */
5321 sipe_plugin_load, /**< load */
5322 sipe_plugin_unload, /**< unload */
5323 sipe_plugin_destroy, /**< destroy */
5324 NULL, /**< ui_info */
5325 &prpl_info, /**< extra_info */
5326 NULL,
5327 sipe_actions,
5328 NULL,
5329 NULL,
5330 NULL,
5331 NULL
5334 static void sipe_plugin_destroy(PurplePlugin *plugin)
5336 GList *entry;
5338 entry = prpl_info.protocol_options;
5339 while (entry) {
5340 purple_account_option_destroy(entry->data);
5341 entry = g_list_delete_link(entry, entry);
5343 prpl_info.protocol_options = NULL;
5345 entry = prpl_info.user_splits;
5346 while (entry) {
5347 purple_account_user_split_destroy(entry->data);
5348 entry = g_list_delete_link(entry, entry);
5350 prpl_info.user_splits = NULL;
5353 static void init_plugin(PurplePlugin *plugin)
5355 PurpleAccountUserSplit *split;
5356 PurpleAccountOption *option;
5358 #ifdef ENABLE_NLS
5359 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5360 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5361 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5362 #endif
5364 purple_plugin_register(plugin);
5366 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5367 purple_account_user_split_set_reverse(split, FALSE);
5368 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5370 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5371 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5372 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5373 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5375 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5376 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5377 // Translators: noun (networking port)
5378 option = purple_account_option_int_new(_("Port"), "port", 5061);
5379 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5381 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5382 purple_account_option_add_list_item(option, _("Auto"), "auto");
5383 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5384 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5385 purple_account_option_add_list_item(option, _("UDP"), "udp");
5386 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5388 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5389 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5391 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5392 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5394 // TODO commented out so won't show in the preferences until we fix krb message signing
5395 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5396 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5398 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5399 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5400 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5403 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5404 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5405 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5406 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5407 my_protocol = plugin;
5410 /* I had to redefined the function for it load, but works */
5411 gboolean purple_init_plugin(PurplePlugin *plugin){
5412 plugin->info = &(info);
5413 init_plugin((plugin));
5414 sipe_plugin_load((plugin));
5415 return purple_plugin_register(plugin);