Fixed the presence process using the flag msrtc_event_categories
[siplcs.git] / src / sipe.c
blobe2123d1b15e1232cbc7fe15ec6fda97ea40ff524
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_status(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 *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1038 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1039 char *to = g_strdup_printf("sip:%s", sip->username);
1040 char *contact = get_contact_register(sip);
1041 char *hdr = g_strdup_printf("Contact: %s\r\n"
1042 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1043 "Event: registration\r\n"
1044 "Allow-Events: presence\r\n"
1045 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1046 "%s", contact, expires);
1047 g_free(contact);
1048 g_free(expires);
1050 sip->registerstatus = 1;
1052 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1053 process_register_response);
1055 g_free(hdr);
1056 g_free(uri);
1057 g_free(to);
1060 static void do_register_cb(struct sipe_account_data *sip)
1062 do_register_exp(sip, -1);
1063 sip->reregister_set = FALSE;
1066 static void do_register(struct sipe_account_data *sip)
1068 do_register_exp(sip, -1);
1072 * Returns URI from provided To or From header.
1074 * Needs to g_free() after use.
1076 * @return URI with sip: prefix
1078 static gchar *parse_from(const gchar *hdr)
1080 gchar *from;
1081 const gchar *tmp, *tmp2 = hdr;
1083 if (!hdr) return NULL;
1084 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1085 tmp = strchr(hdr, '<');
1087 /* i hate the different SIP UA behaviours... */
1088 if (tmp) { /* sip address in <...> */
1089 tmp2 = tmp + 1;
1090 tmp = strchr(tmp2, '>');
1091 if (tmp) {
1092 from = g_strndup(tmp2, tmp - tmp2);
1093 } else {
1094 purple_debug_info("sipe", "found < without > in From\n");
1095 return NULL;
1097 } else {
1098 tmp = strchr(tmp2, ';');
1099 if (tmp) {
1100 from = g_strndup(tmp2, tmp - tmp2);
1101 } else {
1102 from = g_strdup(tmp2);
1105 purple_debug_info("sipe", "got %s\n", from);
1106 return from;
1109 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1111 va_list args;
1112 xmlnode * node = NULL;
1113 const gchar * name;
1115 va_start(args, parent);
1116 while ((name = va_arg(args, const char *)) != NULL) {
1117 node = xmlnode_get_child(parent, name);
1118 if (node == NULL) return NULL;
1119 parent = node;
1121 va_end(args);
1123 return node;
1127 static void
1128 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1130 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1131 send_soap_request(sip, body);
1132 g_free(body);
1135 static void
1136 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1138 if (allow) {
1139 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1140 } else {
1141 purple_debug_info("sipe", "Blocking contact %s\n", who);
1144 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1147 static
1148 void sipe_auth_user_cb(void * data)
1150 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1151 if (!job) return;
1153 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1154 g_free(job);
1157 static
1158 void sipe_deny_user_cb(void * data)
1160 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1161 if (!job) return;
1163 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1164 g_free(job);
1167 static void
1168 sipe_add_permit(PurpleConnection *gc, const char *name)
1170 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1171 sipe_contact_allow_deny(sip, name, TRUE);
1174 static void
1175 sipe_add_deny(PurpleConnection *gc, const char *name)
1177 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1178 sipe_contact_allow_deny(sip, name, FALSE);
1181 /*static void
1182 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1184 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1185 sipe_contact_set_acl(sip, name, "");
1188 static void
1189 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1191 xmlnode *watchers;
1192 xmlnode *watcher;
1193 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1194 if (msg->response != 0 && msg->response != 200) return;
1196 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1198 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1199 if (!watchers) return;
1201 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1202 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1203 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1204 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1206 // TODO pull out optional displayName to pass as alias
1207 if (remote_user) {
1208 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1209 job->who = remote_user;
1210 job->sip = sip;
1211 purple_account_request_authorization(
1212 sip->account,
1213 remote_user,
1214 NULL, // id
1215 alias,
1216 NULL, // message
1217 on_list,
1218 sipe_auth_user_cb,
1219 sipe_deny_user_cb,
1220 (void *) job);
1225 xmlnode_free(watchers);
1226 return;
1229 static void
1230 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1232 PurpleGroup * purple_group = purple_find_group(group->name);
1233 if (!purple_group) {
1234 purple_group = purple_group_new(group->name);
1235 purple_blist_add_group(purple_group, NULL);
1238 if (purple_group) {
1239 group->purple_group = purple_group;
1240 sip->groups = g_slist_append(sip->groups, group);
1241 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1242 } else {
1243 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1247 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1249 struct sipe_group *group;
1250 GSList *entry;
1251 if (sip == NULL) {
1252 return NULL;
1255 entry = sip->groups;
1256 while (entry) {
1257 group = entry->data;
1258 if (group->id == id) {
1259 return group;
1261 entry = entry->next;
1263 return NULL;
1266 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1268 struct sipe_group *group;
1269 GSList *entry;
1270 if (sip == NULL) {
1271 return NULL;
1274 entry = sip->groups;
1275 while (entry) {
1276 group = entry->data;
1277 if (!strcmp(group->name, name)) {
1278 return group;
1280 entry = entry->next;
1282 return NULL;
1285 static void
1286 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1288 gchar *body;
1289 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1290 body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1291 send_soap_request(sip, body);
1292 g_free(body);
1293 g_free(group->name);
1294 group->name = g_strdup(name);
1298 * Only appends if no such value already stored.
1299 * Like Set in Java.
1301 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1302 GSList * res = list;
1303 if (!g_slist_find_custom(list, data, func)) {
1304 res = g_slist_insert_sorted(list, data, func);
1306 return res;
1309 static int
1310 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1311 return group1->id - group2->id;
1315 * Returns string like "2 4 7 8" - group ids buddy belong to.
1317 static gchar *
1318 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1319 int i = 0;
1320 gchar *res;
1321 //creating array from GList, converting int to gchar*
1322 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1323 GSList *entry = buddy->groups;
1324 while (entry) {
1325 struct sipe_group * group = entry->data;
1326 ids_arr[i] = g_strdup_printf("%d", group->id);
1327 entry = entry->next;
1328 i++;
1330 ids_arr[i] = NULL;
1331 res = g_strjoinv(" ", ids_arr);
1332 g_strfreev(ids_arr);
1333 return res;
1337 * Sends buddy update to server
1339 static void
1340 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1342 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1343 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1345 if (buddy && purple_buddy) {
1346 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1347 gchar *body;
1348 gchar *groups = sipe_get_buddy_groups_string(buddy);
1349 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1351 body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1352 alias, groups, "true", buddy->name, sip->contacts_delta++
1354 send_soap_request(sip, body);
1355 g_free(groups);
1356 g_free(body);
1360 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1362 if (msg->response == 200) {
1363 struct sipe_group *group;
1364 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1365 xmlnode *xml;
1366 xmlnode *node;
1367 char *group_id;
1368 struct sipe_buddy *buddy;
1369 group->name = ctx->group_name;
1371 xml = xmlnode_from_str(msg->body, msg->bodylen);
1372 if (!xml) {
1373 g_free(ctx);
1374 return FALSE;
1377 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1378 if (!node) {
1379 g_free(ctx);
1380 xmlnode_free(xml);
1381 return FALSE;
1384 group_id = xmlnode_get_data(node);
1385 if (!group_id) {
1386 g_free(ctx);
1387 xmlnode_free(xml);
1388 return FALSE;
1391 group = g_new0(struct sipe_group, 1);
1392 group->id = (int)g_ascii_strtod(group_id, NULL);
1393 g_free(group_id);
1395 sipe_group_add(sip, group);
1397 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1398 if (buddy) {
1399 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1402 sipe_group_set_user(sip, ctx->user_name);
1404 g_free(ctx);
1405 xmlnode_free(xml);
1406 return TRUE;
1408 return FALSE;
1411 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1413 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1414 gchar *body;
1415 ctx->group_name = g_strdup(name);
1416 ctx->user_name = g_strdup(who);
1418 body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1419 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1420 g_free(body);
1424 * A timer callback
1425 * Should return FALSE if repetitive action is not needed
1427 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1429 gboolean ret;
1430 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1431 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1432 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1433 (sched_action->action)(sched_action->sip, sched_action->payload);
1434 ret = sched_action->repetitive;
1435 g_free(sched_action->payload);
1436 g_free(sched_action->name);
1437 g_free(sched_action);
1438 return ret;
1442 * Do schedule action for execution in the future.
1443 * Non repetitive execution.
1445 * @param name of action (will be copied)
1446 * @param timeout in seconds
1447 * @action callback function
1448 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1450 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1452 struct scheduled_action *sched_action;
1454 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1455 sched_action = g_new0(struct scheduled_action, 1);
1456 sched_action->repetitive = FALSE;
1457 sched_action->name = g_strdup(name);
1458 sched_action->action = action;
1459 sched_action->sip = sip;
1460 sched_action->payload = payload;
1461 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1462 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1463 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1467 * Kills action timer effectively cancelling
1468 * scheduled action
1470 * @param name of action
1472 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1474 GSList *entry;
1476 if (!sip->timeouts || !name) return;
1478 entry = sip->timeouts;
1479 while (entry) {
1480 struct scheduled_action *sched_action = entry->data;
1481 if(!strcmp(sched_action->name, name)) {
1482 GSList *to_delete = entry;
1483 entry = entry->next;
1484 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1485 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1486 purple_timeout_remove(sched_action->timeout_handler);
1487 g_free(sched_action->payload);
1488 g_free(sched_action->name);
1489 g_free(sched_action);
1490 } else {
1491 entry = entry->next;
1496 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1498 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1500 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1502 process_incoming_notify(sip, msg, FALSE, FALSE);
1504 return TRUE;
1507 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1509 gchar *tmp = *resources_uri;
1510 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1511 g_free(tmp);
1515 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1516 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1517 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1518 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1519 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1522 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip){
1523 gchar *to = g_strdup_printf("sip:%s", sip->username);
1524 gchar *contact = get_contact(sip);
1525 gchar *request;
1526 gchar *content;
1527 gchar *resources_uri = g_strdup("");
1528 gchar *require = "";
1529 gchar *accept = "";
1530 gchar *content_type;
1532 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1534 if (sip->msrtc_event_categories) {
1535 require = ", categoryList";
1536 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1537 content_type = "application/msrtc-adrl-categorylist+xml";
1538 content = g_strdup_printf(
1539 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1540 "<action name=\"subscribe\" id=\"63792024\">\n"
1541 "<adhocList>\n%s</adhocList>\n"
1542 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1543 "<category name=\"note\"/>\n"
1544 "<category name=\"state\"/>\n"
1545 "</categoryList>\n"
1546 "</action>\n"
1547 "</batchSub>", sip->username, resources_uri);
1548 } else {
1549 content_type = "application/adrl+xml";
1550 content = g_strdup_printf(
1551 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1552 "<create xmlns=\"\">\n%s</create>\n"
1553 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1555 g_free(resources_uri);
1557 request = g_strdup_printf(
1558 "Require: adhoclist%s\r\n"
1559 "Supported: eventlist\r\n"
1560 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1561 "Supported: ms-piggyback-first-notify\r\n"
1562 "Supported: com.microsoft.autoextend\r\n"
1563 "Supported: ms-benotify\r\n"
1564 "Proxy-Require: ms-benotify\r\n"
1565 "Event: presence\r\n"
1566 "Content-Type: %s\r\n"
1567 "Contact: %s\r\n", require, accept, content_type, contact);
1568 g_free(contact);
1570 /* subscribe to buddy presence */
1571 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1572 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1574 g_free(content);
1575 g_free(to);
1576 g_free(request);
1580 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1581 * The user sends a single SUBSCRIBE request to the subscribed contact.
1582 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1586 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1588 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1589 gchar *tmp = get_contact(sip);
1590 gchar *request;
1591 gchar *content;
1592 request = g_strdup_printf(
1593 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1594 "Supported: ms-piggyback-first-notify\r\n"
1595 "Supported: com.microsoft.autoextend\r\n"
1596 "Supported: ms-benotify\r\n"
1597 "Proxy-Require: ms-benotify\r\n"
1598 "Event: presence\r\n"
1599 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1600 "Contact: %s\r\n", tmp);
1602 content = g_strdup_printf(
1603 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1604 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1605 "<resource uri=\"%s\"/>\n"
1606 "</adhocList>\n"
1607 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1608 "<category name=\"note\"/>\n"
1609 "<category name=\"state\"/>\n"
1610 "</categoryList>\n"
1611 "</action>\n"
1612 "</batchSub>", sip->username, to
1615 g_free(tmp);
1617 /* subscribe to buddy presence */
1618 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1620 g_free(content);
1621 g_free(to);
1622 g_free(request);
1625 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1627 if (!purple_status_is_active(status))
1628 return;
1630 if (account->gc) {
1631 struct sipe_account_data *sip = account->gc->proto_data;
1633 if (sip) {
1634 g_free(sip->status);
1635 sip->status = g_strdup(purple_status_get_id(status));
1636 send_presence_status(sip);
1641 static void
1642 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1644 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1645 sipe_group_set_user(sip, name);
1648 static void
1649 sipe_group_buddy(PurpleConnection *gc,
1650 const char *who,
1651 const char *old_group_name,
1652 const char *new_group_name)
1654 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1655 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1656 struct sipe_group * old_group = NULL;
1657 struct sipe_group * new_group;
1659 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1660 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1662 if(!buddy) { // buddy not in roaming list
1663 return;
1666 if (old_group_name) {
1667 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1669 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1671 if (old_group) {
1672 buddy->groups = g_slist_remove(buddy->groups, old_group);
1673 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1676 if (!new_group) {
1677 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1678 } else {
1679 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1680 sipe_group_set_user(sip, who);
1684 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1686 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1687 struct sipe_buddy *b;
1689 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1691 // Prepend sip: if needed
1692 if (strncmp("sip:", buddy->name, 4)) {
1693 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1694 purple_blist_rename_buddy(buddy, buf);
1695 g_free(buf);
1698 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1699 b = g_new0(struct sipe_buddy, 1);
1700 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1701 b->name = g_strdup(buddy->name);
1702 g_hash_table_insert(sip->buddies, b->name, b);
1703 sipe_group_buddy(gc, b->name, NULL, group->name);
1704 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1705 } else {
1706 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1710 static void sipe_free_buddy(struct sipe_buddy *buddy)
1712 g_free(buddy->name);
1713 g_free(buddy->annotation);
1714 g_free(buddy->device_name);
1715 g_slist_free(buddy->groups);
1716 g_free(buddy);
1720 * Unassociates buddy from group first.
1721 * Then see if no groups left, removes buddy completely.
1722 * Otherwise updates buddy groups on server.
1724 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1726 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1727 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1728 struct sipe_group *g = NULL;
1730 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1732 if (!b) return;
1734 if (group) {
1735 g = sipe_group_find_by_name(sip, group->name);
1738 if (g) {
1739 b->groups = g_slist_remove(b->groups, g);
1740 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1743 if (g_slist_length(b->groups) < 1) {
1744 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1745 sipe_cancel_scheduled_action(sip, action_name);
1746 g_free(action_name);
1748 g_hash_table_remove(sip->buddies, buddy->name);
1750 if (b->name) {
1751 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1752 send_soap_request(sip, body);
1753 g_free(body);
1756 sipe_free_buddy(b);
1757 } else {
1758 //updates groups on server
1759 sipe_group_set_user(sip, b->name);
1764 static void
1765 sipe_rename_group(PurpleConnection *gc,
1766 const char *old_name,
1767 PurpleGroup *group,
1768 GList *moved_buddies)
1770 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1771 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1772 if (group) {
1773 sipe_group_rename(sip, s_group, group->name);
1774 } else {
1775 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1779 static void
1780 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1782 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1783 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1784 if (s_group) {
1785 gchar *body;
1786 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1787 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1788 send_soap_request(sip, body);
1789 g_free(body);
1791 sip->groups = g_slist_remove(sip->groups, s_group);
1792 g_free(s_group->name);
1793 } else {
1794 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1798 static GList *sipe_status_types(PurpleAccount *acc)
1800 PurpleStatusType *type;
1801 GList *types = NULL;
1803 // Online
1804 type = purple_status_type_new_with_attrs(
1805 PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE,
1806 // Translators: noun
1807 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1808 NULL);
1809 types = g_list_append(types, type);
1811 // Busy
1812 type = purple_status_type_new_with_attrs(
1813 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1814 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1815 NULL);
1816 types = g_list_append(types, type);
1818 // Do Not Disturb (Not let user set it)
1819 type = purple_status_type_new_with_attrs(
1820 PURPLE_STATUS_UNAVAILABLE, "do-not-disturb", "Do Not Disturb", TRUE, FALSE, FALSE,
1821 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1822 NULL);
1823 types = g_list_append(types, type);
1825 // Be Right Back
1826 type = purple_status_type_new_with_attrs(
1827 PURPLE_STATUS_AWAY, "be-right-back", _("Be Right Back"), TRUE, TRUE, FALSE,
1828 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1829 NULL);
1830 types = g_list_append(types, type);
1832 // Away
1833 type = purple_status_type_new_with_attrs(
1834 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1835 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1836 NULL);
1837 types = g_list_append(types, type);
1839 //On The Phone
1840 type = purple_status_type_new_with_attrs(
1841 PURPLE_STATUS_UNAVAILABLE, "on-the-phone", _("On The Phone"), TRUE, TRUE, FALSE,
1842 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1843 NULL);
1844 types = g_list_append(types, type);
1846 //Out To Lunch
1847 type = purple_status_type_new_with_attrs(
1848 PURPLE_STATUS_AWAY, "out-to-lunch", "Out To Lunch", TRUE, TRUE, FALSE,
1849 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1850 NULL);
1851 types = g_list_append(types, type);
1853 //Appear Offline
1854 type = purple_status_type_new_full(
1855 PURPLE_STATUS_INVISIBLE, NULL, "Appear Offline", TRUE, TRUE, FALSE);
1856 types = g_list_append(types, type);
1858 // Offline
1859 type = purple_status_type_new_full(
1860 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1861 types = g_list_append(types, type);
1863 return types;
1867 * A callback for g_hash_table_foreach
1869 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1871 sipe_subscribe_presence_single(sip, buddy->name);
1875 * Removes entries from purple buddy list
1876 * that does not correspond ones in the roaming contact list.
1878 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1879 GSList *buddies = purple_find_buddies(sip->account, NULL);
1880 GSList *entry = buddies;
1881 struct sipe_buddy *buddy;
1882 PurpleBuddy *b;
1883 PurpleGroup *g;
1885 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1886 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1887 while (entry) {
1888 b = entry->data;
1889 g = purple_buddy_get_group(b);
1890 buddy = g_hash_table_lookup(sip->buddies, b->name);
1891 if(buddy) {
1892 gboolean in_sipe_groups = FALSE;
1893 GSList *entry2 = buddy->groups;
1894 while (entry2) {
1895 struct sipe_group *group = entry2->data;
1896 if (!strcmp(group->name, g->name)) {
1897 in_sipe_groups = TRUE;
1898 break;
1900 entry2 = entry2->next;
1902 if(!in_sipe_groups) {
1903 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1904 purple_blist_remove_buddy(b);
1906 } else {
1907 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1908 purple_blist_remove_buddy(b);
1910 entry = entry->next;
1914 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1916 int len = msg->bodylen;
1918 gchar *tmp = sipmsg_find_header(msg, "Event");
1919 xmlnode *item;
1920 xmlnode *isc;
1921 const gchar *contacts_delta;
1922 xmlnode *group_node;
1923 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1924 return FALSE;
1927 /* Convert the contact from XML to Purple Buddies */
1928 isc = xmlnode_from_str(msg->body, len);
1929 if (!isc) {
1930 return FALSE;
1933 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1934 if (contacts_delta) {
1935 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1938 /* Parse groups */
1939 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1940 struct sipe_group * group = g_new0(struct sipe_group, 1);
1941 const char *name = xmlnode_get_attrib(group_node, "name");
1943 if (!strncmp(name, "~", 1)) {
1944 // TODO translate
1945 name = "Other Contacts";
1947 group->name = g_strdup(name);
1948 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1950 sipe_group_add(sip, group);
1953 // Make sure we have at least one group
1954 if (g_slist_length(sip->groups) == 0) {
1955 struct sipe_group * group = g_new0(struct sipe_group, 1);
1956 PurpleGroup *purple_group;
1957 // TODO translate
1958 group->name = g_strdup("Other Contacts");
1959 group->id = 1;
1960 purple_group = purple_group_new(group->name);
1961 purple_blist_add_group(purple_group, NULL);
1962 sip->groups = g_slist_append(sip->groups, group);
1965 /* Parse contacts */
1966 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1967 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1968 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1969 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1970 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1971 gchar **item_groups;
1972 struct sipe_group *group = NULL;
1973 struct sipe_buddy *buddy = NULL;
1974 int i = 0;
1976 // assign to group Other Contacts if nothing else received
1977 if(!groups || !strcmp("", groups) ) {
1978 group = sipe_group_find_by_name(sip, "Other Contacts");
1979 groups = group ? g_strdup_printf("%d", group->id) : "1";
1982 item_groups = g_strsplit(groups, " ", 0);
1984 while (item_groups[i]) {
1985 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1987 // If couldn't find the right group for this contact, just put them in the first group we have
1988 if (group == NULL && g_slist_length(sip->groups) > 0) {
1989 group = sip->groups->data;
1992 if (group != NULL) {
1993 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1994 if (!b){
1995 b = purple_buddy_new(sip->account, buddy_name, uri);
1996 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1999 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2000 if (name != NULL && strlen(name) != 0) {
2001 purple_blist_alias_buddy(b, name);
2005 if (!buddy) {
2006 buddy = g_new0(struct sipe_buddy, 1);
2007 buddy->name = g_strdup(b->name);
2008 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2011 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2013 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2014 } else {
2015 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2016 name);
2019 i++;
2020 } // while, contact groups
2021 g_strfreev(item_groups);
2022 g_free(groups);
2023 g_free(name);
2024 g_free(buddy_name);
2025 g_free(uri);
2027 } // for, contacts
2029 xmlnode_free(isc);
2031 sipe_cleanup_local_blist(sip);
2033 //subscribe to buddies
2034 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2035 //if(sip->msrtc_event_categories){
2036 sipe_subscribe_presence_batched(sip);
2037 //}else{
2038 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2040 sip->subscribed_buddies = TRUE;
2043 return 0;
2047 * Subscribe roaming contacts
2049 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2051 gchar *to = g_strdup_printf("sip:%s", sip->username);
2052 gchar *tmp = get_contact(sip);
2053 gchar *hdr = g_strdup_printf(
2054 "Event: vnd-microsoft-roaming-contacts\r\n"
2055 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2056 "Supported: com.microsoft.autoextend\r\n"
2057 "Supported: ms-benotify\r\n"
2058 "Proxy-Require: ms-benotify\r\n"
2059 "Supported: ms-piggyback-first-notify\r\n"
2060 "Contact: %s\r\n", tmp);
2061 g_free(tmp);
2063 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2064 g_free(to);
2065 g_free(hdr);
2068 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2070 gchar *to = g_strdup_printf("sip:%s", sip->username);
2071 gchar *tmp = get_contact(sip);
2072 gchar *hdr = g_strdup_printf(
2073 "Event: presence.wpending\r\n"
2074 "Accept: text/xml+msrtc.wpending\r\n"
2075 "Supported: com.microsoft.autoextend\r\n"
2076 "Supported: ms-benotify\r\n"
2077 "Proxy-Require: ms-benotify\r\n"
2078 "Supported: ms-piggyback-first-notify\r\n"
2079 "Contact: %s\r\n", tmp);
2080 g_free(tmp);
2082 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2083 g_free(to);
2084 g_free(hdr);
2087 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2089 const gchar *contacts_delta;
2090 xmlnode *xml;
2092 xml = xmlnode_from_str(msg->body, msg->bodylen);
2093 if (!xml)
2095 return;
2098 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2099 if (contacts_delta)
2101 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2104 xmlnode_free(xml);
2108 * When we receive some self (BE) NOTIFY with a new subscriber
2109 * we sends a setSubscribers request to him [SIP-PRES]
2113 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2115 gchar *contact;
2116 gchar *to;
2117 xmlnode *xml;
2118 xmlnode *node;
2120 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2122 xml = xmlnode_from_str(msg->body, msg->bodylen);
2123 if (!xml) return;
2125 contact = get_contact(sip);
2126 to = g_strdup_printf("sip:%s", sip->username);
2128 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2129 const char *user;
2130 gchar *hdr;
2131 gchar *body;
2133 user = xmlnode_get_attrib(node, "user");
2134 if (!user) continue;
2136 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2138 hdr = g_strdup_printf(
2139 "Contact: %s\r\n"
2140 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2142 body = g_strdup_printf(
2143 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2144 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2145 "</setSubscribers>", user);
2147 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2148 g_free(body);
2149 g_free(hdr);
2152 g_free(to);
2153 g_free(contact);
2154 xmlnode_free(xml);
2157 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2159 gchar *to = g_strdup_printf("sip:%s", sip->username);
2160 gchar *tmp = get_contact(sip);
2161 gchar *hdr = g_strdup_printf(
2162 "Event: vnd-microsoft-roaming-ACL\r\n"
2163 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2164 "Supported: com.microsoft.autoextend\r\n"
2165 "Supported: ms-benotify\r\n"
2166 "Proxy-Require: ms-benotify\r\n"
2167 "Supported: ms-piggyback-first-notify\r\n"
2168 "Contact: %s\r\n", tmp);
2169 g_free(tmp);
2171 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2172 g_free(to);
2173 g_free(hdr);
2177 * To request for presence information about the user, access level settings that have already been configured by the user
2178 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2179 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2182 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2184 gchar *to = g_strdup_printf("sip:%s", sip->username);
2185 gchar *tmp = get_contact(sip);
2186 gchar *hdr = g_strdup_printf(
2187 "Event: vnd-microsoft-roaming-self\r\n"
2188 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2189 "Supported: com.microsoft.autoextend\r\n"
2190 "Supported: ms-benotify\r\n"
2191 "Proxy-Require: ms-benotify\r\n"
2192 "Supported: ms-piggyback-first-notify\r\n"
2193 "Contact: %s\r\n"
2194 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2196 gchar *body=g_strdup(
2197 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2198 "<roaming type=\"categories\"/>"
2199 "<roaming type=\"containers\"/>"
2200 "<roaming type=\"subscribers\"/></roamingList>");
2202 g_free(tmp);
2203 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2204 g_free(body);
2205 g_free(to);
2206 g_free(hdr);
2209 /** Subscription for provisioning information to help with initial
2210 * configuration. This subscription is a one-time query (denoted by the Expires header,
2211 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2212 * configuration, meeting policies, and policy settings that Communicator must enforce.
2213 * TODO: for what we need this information.
2216 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2218 gchar *to = g_strdup_printf("sip:%s", sip->username);
2219 gchar *tmp = get_contact(sip);
2220 gchar *hdr = g_strdup_printf(
2221 "Event: vnd-microsoft-provisioning-v2\r\n"
2222 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2223 "Supported: com.microsoft.autoextend\r\n"
2224 "Supported: ms-benotify\r\n"
2225 "Proxy-Require: ms-benotify\r\n"
2226 "Supported: ms-piggyback-first-notify\r\n"
2227 "Expires: 0\r\n"
2228 "Contact: %s\r\n"
2229 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2230 gchar *body = g_strdup(
2231 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2232 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2233 "<provisioningGroup name=\"ucPolicy\"/>"
2234 "</provisioningGroupList>");
2236 g_free(tmp);
2237 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2238 g_free(body);
2239 g_free(to);
2240 g_free(hdr);
2243 /* IM Session (INVITE and MESSAGE methods) */
2245 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2247 struct sip_im_session *session;
2248 GSList *entry;
2249 if (sip == NULL || who == NULL) {
2250 return NULL;
2253 entry = sip->im_sessions;
2254 while (entry) {
2255 session = entry->data;
2256 if ((who != NULL && !strcmp(who, session->with))) {
2257 return session;
2259 entry = entry->next;
2261 return NULL;
2264 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2266 struct sip_im_session *session = find_im_session(sip, who);
2267 if (!session) {
2268 session = g_new0(struct sip_im_session, 1);
2269 session->with = g_strdup(who);
2270 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2272 return session;
2275 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2277 struct sip_dialog *dialog = session->dialog;
2278 GSList *entry;
2280 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2282 if (dialog) {
2283 entry = dialog->routes;
2284 while (entry) {
2285 g_free(entry->data);
2286 entry = g_slist_remove(entry, entry->data);
2288 entry = dialog->supported;
2289 while (entry) {
2290 g_free(entry->data);
2291 entry = g_slist_remove(entry, entry->data);
2293 g_free(dialog->callid);
2294 g_free(dialog->ourtag);
2295 g_free(dialog->theirtag);
2296 g_free(dialog->theirepid);
2297 g_free(dialog->request);
2299 g_free(session->dialog);
2301 entry = session->outgoing_message_queue;
2302 while (entry) {
2303 g_free(entry->data);
2304 entry = g_slist_remove(entry, entry->data);
2307 g_free(session->with);
2308 g_free(session);
2311 static gboolean
2312 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2314 gboolean ret = TRUE;
2316 if (msg->response != 200) {
2317 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2318 return FALSE;
2321 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2323 return ret;
2327 * Asks UA/proxy about its capabilities.
2329 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2331 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2332 gchar *contact = get_contact(sip);
2333 gchar *request;
2334 request = g_strdup_printf(
2335 "Accept: application/sdp\r\n"
2336 "Contact: %s\r\n", contact);
2338 g_free(contact);
2340 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2342 g_free(to);
2343 g_free(request);
2346 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2348 char *msg, *msg_tmp;
2349 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2350 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2351 g_free(msg_tmp);
2352 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2353 "possibly because one or more persons are offline:\n%s") ,
2354 msg ? msg : "");
2355 purple_conv_present_error(with, sip->account, msg_tmp);
2356 g_free(msg);
2357 g_free(msg_tmp);
2360 static void sipe_im_remove_first_from_queue (struct sip_im_session * session);
2361 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2363 static gboolean
2364 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2366 gboolean ret = TRUE;
2367 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2368 struct sip_im_session * session = find_im_session(sip, with);
2369 struct sip_dialog *dialog;
2371 if (!session) {
2372 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2373 g_free(with);
2374 return FALSE;
2377 if (msg->response != 200) {
2378 gchar *queued_msg = NULL;
2379 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2381 if (session->outgoing_message_queue) {
2382 queued_msg = session->outgoing_message_queue->data;
2384 sipe_present_message_undelivered_err(with, sip, queued_msg);
2385 im_session_destroy(sip, session);
2386 g_free(with);
2387 return FALSE;
2390 dialog = session->dialog;
2391 if (!dialog) {
2392 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2393 ret = FALSE;
2396 sipe_im_remove_first_from_queue(session);
2397 sipe_im_process_queue(sip, session);
2398 g_free(with);
2399 return ret;
2402 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2404 gchar *hdr;
2405 gchar *fullto;
2406 gchar *tmp;
2407 char *msgformat;
2408 char *msgtext;
2409 gchar *msgr_value;
2410 gchar *msgr;
2412 if (strncmp("sip:", session->with, 4)) {
2413 fullto = g_strdup_printf("sip:%s", session->with);
2414 } else {
2415 fullto = g_strdup(session->with);
2418 sipe_parse_html(msg, &msgformat, &msgtext);
2419 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2421 msgr_value = sipmsg_get_msgr_string(msgformat);
2422 g_free(msgformat);
2423 if (msgr_value) {
2424 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2425 g_free(msgr_value);
2426 } else {
2427 msgr = g_strdup("");
2430 tmp = get_contact(sip);
2431 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2432 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2433 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2434 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2435 tmp, msgr);
2436 g_free(tmp);
2437 g_free(msgr);
2439 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2440 g_free(msgtext);
2441 g_free(hdr);
2442 g_free(fullto);
2446 static void
2447 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2449 GSList *entry = session->outgoing_message_queue;
2450 if (entry) {
2451 char *queued_msg = entry->data;
2452 sipe_send_message(sip, session, queued_msg);
2456 static void
2457 sipe_im_remove_first_from_queue (struct sip_im_session * session)
2459 if (session && session->outgoing_message_queue) {
2460 char *queued_msg = session->outgoing_message_queue->data;
2461 // Remove from the queue and free the string
2462 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2463 g_free(queued_msg);
2467 static void
2468 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2470 GSList *hdr = msg->headers;
2471 struct siphdrelement *elem;
2472 gchar *contact;
2474 while(hdr)
2476 elem = hdr->data;
2477 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2479 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2480 dialog->routes = g_slist_append(dialog->routes, route);
2482 hdr = g_slist_next(hdr);
2485 if (outgoing)
2487 dialog->routes = g_slist_reverse(dialog->routes);
2490 if (dialog->routes)
2492 dialog->request = dialog->routes->data;
2493 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2496 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2497 dialog->routes = g_slist_append(dialog->routes, contact);
2500 static void
2501 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2503 GSList *hdr = msg->headers;
2504 struct siphdrelement *elem;
2505 while(hdr)
2507 elem = hdr->data;
2508 if(!g_ascii_strcasecmp(elem->name, "Supported")
2509 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2511 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2514 hdr = g_slist_next(hdr);
2518 static void
2519 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2521 gchar *us = outgoing ? "From" : "To";
2522 gchar *them = outgoing ? "To" : "From";
2524 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2525 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2526 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2527 if (!dialog->theirepid) {
2528 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2530 if (!dialog->theirepid) {
2531 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2534 sipe_get_route_header(msg, dialog, outgoing);
2535 sipe_get_supported_header(msg, dialog, outgoing);
2539 static gboolean
2540 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2542 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2543 struct sip_im_session * session = find_im_session(sip, with);
2544 struct sip_dialog *dialog;
2546 if (!session) {
2547 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2548 g_free(with);
2549 return FALSE;
2552 if (msg->response != 200) {
2553 gchar *queued_msg = NULL;
2554 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2556 if (session->outgoing_message_queue) {
2557 queued_msg = session->outgoing_message_queue->data;
2559 sipe_present_message_undelivered_err(with, sip, queued_msg);
2561 im_session_destroy(sip, session);
2562 g_free(with);
2563 return FALSE;
2566 dialog = session->dialog;
2567 if (!dialog) {
2568 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2569 g_free(with);
2570 return FALSE;
2573 sipe_parse_dialog(msg, dialog, TRUE);
2574 dialog->cseq = 0;
2576 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2577 session->outgoing_invite = NULL;
2578 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2579 sipe_im_remove_first_from_queue(session);
2580 } else {
2581 sipe_im_process_queue(sip, session);
2584 g_free(with);
2585 return TRUE;
2589 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
2591 gchar *hdr;
2592 gchar *to;
2593 gchar *contact;
2594 gchar *body;
2595 char *msgformat;
2596 char *msgtext;
2597 char *base64_msg;
2598 char *ms_text_format;
2599 gchar *msgr_value;
2600 gchar *msgr;
2602 if (session->dialog) {
2603 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2604 return;
2607 session->dialog = g_new0(struct sip_dialog, 1);
2609 if (strstr(session->with, "sip:")) {
2610 to = g_strdup(session->with);
2611 } else {
2612 to = g_strdup_printf("sip:%s", session->with);
2615 sipe_parse_html(msg_body, &msgformat, &msgtext);
2616 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2618 msgr_value = sipmsg_get_msgr_string(msgformat);
2619 g_free(msgformat);
2620 msgr = "";
2621 if (msgr_value) {
2622 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2623 g_free(msgr_value);
2626 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2627 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2628 g_free(msgtext);
2629 g_free(msgr);
2630 g_free(base64_msg);
2632 contact = get_contact(sip);
2633 hdr = g_strdup_printf(
2634 "Contact: %s\r\n%s"
2635 "Content-Type: application/sdp\r\n",
2636 contact, ms_text_format);
2637 g_free(ms_text_format);
2639 body = g_strdup_printf(
2640 "v=0\r\n"
2641 "o=- 0 0 IN IP4 %s\r\n"
2642 "s=session\r\n"
2643 "c=IN IP4 %s\r\n"
2644 "t=0 0\r\n"
2645 "m=message %d sip null\r\n"
2646 "a=accept-types:text/plain text/html image/gif "
2647 "multipart/alternative application/im-iscomposing+xml\r\n",
2648 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2650 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2651 to, to, hdr, body, session->dialog, process_invite_response);
2653 g_free(to);
2654 g_free(body);
2655 g_free(hdr);
2656 g_free(contact);
2659 static void
2660 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2662 if (session) {
2663 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2664 im_session_destroy(sip, session);
2668 static void
2669 sipe_convo_closed(PurpleConnection * gc, const char *who)
2671 struct sipe_account_data *sip = gc->proto_data;
2673 purple_debug_info("sipe", "conversation with %s closed\n", who);
2674 im_session_close(sip, find_im_session(sip, who));
2677 static void
2678 im_session_close_all (struct sipe_account_data *sip)
2680 GSList *entry = sip->im_sessions;
2681 while (entry) {
2682 im_session_close (sip, entry->data);
2683 entry = sip->im_sessions;
2687 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2689 struct sipe_account_data *sip;
2690 gchar *to;
2691 gchar *text;
2692 struct sip_im_session *session;
2694 sip = gc->proto_data;
2695 to = g_strdup(who);
2696 text = g_strdup(what);
2698 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2700 session = find_or_create_im_session(sip, who);
2702 // Queue the message
2703 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
2705 if (session->dialog && session->dialog->callid) {
2706 sipe_im_process_queue(sip, session);
2707 } else if (!session->outgoing_invite) {
2708 // Need to send the INVITE to get the outgoing dialog setup
2709 sipe_invite(sip, session, text);
2712 g_free(to);
2713 g_free(text);
2714 return 1;
2718 /* End IM Session (INVITE and MESSAGE methods) */
2720 static unsigned int
2721 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2723 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2724 struct sip_im_session *session;
2726 if (state == PURPLE_NOT_TYPING)
2727 return 0;
2729 session = find_im_session(sip, who);
2731 if (session && session->dialog) {
2732 send_sip_request(gc, "INFO", who, who,
2733 "Content-Type: application/xml\r\n",
2734 SIPE_SEND_TYPING, session->dialog, NULL);
2737 return SIPE_TYPING_SEND_TIMEOUT;
2740 static gboolean resend_timeout(struct sipe_account_data *sip)
2742 GSList *tmp = sip->transactions;
2743 time_t currtime = time(NULL);
2744 while (tmp) {
2745 struct transaction *trans = tmp->data;
2746 tmp = tmp->next;
2747 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2748 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2749 /* TODO 408 */
2750 } else {
2751 if ((currtime - trans->time > 2) && trans->retries == 0) {
2752 trans->retries++;
2753 sendout_sipmsg(sip, trans->msg);
2757 return TRUE;
2760 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2762 /* register again when security token expires */
2763 /* we have to start a new authentication as the security token
2764 * is almost expired by sending a not signed REGISTER message */
2765 purple_debug_info("sipe", "do a full reauthentication\n");
2766 sipe_auth_free(&sip->registrar);
2767 sip->registerstatus = 0;
2768 do_register(sip);
2769 sip->reauthenticate_set = FALSE;
2772 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2774 gchar *from;
2775 gchar *contenttype;
2776 gboolean found = FALSE;
2778 from = parse_from(sipmsg_find_header(msg, "From"));
2780 if (!from) return;
2782 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2784 contenttype = sipmsg_find_header(msg, "Content-Type");
2785 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2786 gchar *msgr = sipmsg_find_part_of_header(contenttype, "msgr=", NULL, NULL);
2787 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2789 gchar *body_esc = g_markup_escape_text(msg->body, -1);
2790 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2791 g_free(msgr);
2792 g_free(body_esc);
2793 g_free(x_mms_im_format);
2795 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2796 g_free(body_html);
2797 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2798 found = TRUE;
2799 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2800 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2801 xmlnode *state;
2802 gchar *statedata;
2804 if (!isc) {
2805 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2806 return;
2809 state = xmlnode_get_child(isc, "state");
2811 if (!state) {
2812 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2813 xmlnode_free(isc);
2814 return;
2817 statedata = xmlnode_get_data(state);
2818 if (statedata) {
2819 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2820 else serv_got_typing_stopped(sip->gc, from);
2822 g_free(statedata);
2824 xmlnode_free(isc);
2825 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2826 found = TRUE;
2828 if (!found) {
2829 purple_debug_info("sipe", "got unknown mime-type");
2830 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2832 g_free(from);
2835 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2837 gchar *ms_text_format;
2838 gchar *from;
2839 gchar *body;
2840 struct sip_im_session *session;
2842 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
2844 // Only accept text invitations
2845 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2846 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2847 return;
2850 from = parse_from(sipmsg_find_header(msg, "From"));
2851 session = find_or_create_im_session (sip, from);
2852 if (session) {
2853 if (session->dialog) {
2854 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2855 } else {
2856 session->dialog = g_new0(struct sip_dialog, 1);
2858 sipe_parse_dialog(msg, session->dialog, FALSE);
2860 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2861 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2862 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2863 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2865 } else {
2866 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2869 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2870 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2871 if (ms_text_format && !strncmp(ms_text_format, "text/plain", 10)) {
2872 gchar *msgr = sipmsg_find_part_of_header(ms_text_format, "msgr=", ";", NULL);
2873 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2875 gchar *ms_body = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
2876 g_free(msgr);
2877 if (ms_body) {
2878 gchar *body = purple_base64_decode(ms_body, NULL);
2879 gchar *body_esc = g_markup_escape_text(body, -1);
2880 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2881 g_free(ms_body);
2882 g_free(body_esc);
2883 g_free(body);
2884 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2885 g_free(body_html);
2886 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2888 g_free(x_mms_im_format);
2890 g_free(from);
2892 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2893 sipmsg_remove_header(msg, "Ms-Text-Format");
2894 sipmsg_remove_header(msg, "EndPoints");
2895 sipmsg_remove_header(msg, "User-Agent");
2896 sipmsg_remove_header(msg, "Roster-Manager");
2898 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2899 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2901 body = g_strdup_printf(
2902 "v=0\r\n"
2903 "o=- 0 0 IN IP4 %s\r\n"
2904 "s=session\r\n"
2905 "c=IN IP4 %s\r\n"
2906 "t=0 0\r\n"
2907 "m=message %d sip sip:%s\r\n"
2908 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2909 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2910 sip->realport, sip->username);
2911 send_sip_response(sip->gc, msg, 200, "OK", body);
2912 g_free(body);
2915 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
2917 gchar *from;
2918 gchar *body;
2919 struct sip_im_session *session;
2922 from = parse_from(sipmsg_find_header(msg, "From"));
2923 session = find_or_create_im_session (sip, from);
2924 if (session) {
2925 if (session->dialog) {
2926 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2927 } else {
2928 session->dialog = g_new0(struct sip_dialog, 1);
2930 sipe_parse_dialog(msg, session->dialog, FALSE);
2932 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2933 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2934 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2935 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2937 } else {
2938 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2941 g_free(from);
2943 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2944 sipmsg_remove_header(msg, "EndPoints");
2945 sipmsg_remove_header(msg, "User-Agent");
2947 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
2948 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2950 body = g_strdup_printf(
2951 "v=0\r\n"
2952 "o=- 0 0 IN IP4 0.0.0.0\r\n"
2953 "s=session\r\n"
2954 "c=IN IP4 0.0.0.0\r\n"
2955 "t=0 0\r\n"
2956 "m=message %d sip sip:%s\r\n"
2957 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2958 sip->realport, sip->username);
2959 send_sip_response(sip->gc, msg, 200, "OK", body);
2960 g_free(body);
2963 static void sipe_connection_cleanup(struct sipe_account_data *);
2964 static void create_connection(struct sipe_account_data *, gchar *, int);
2966 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2968 gchar *tmp;
2969 //gchar krb5_token;
2970 const gchar *expires_header;
2971 int expires;
2972 GSList *hdr = msg->headers;
2973 struct siphdrelement *elem;
2975 expires_header = sipmsg_find_header(msg, "Expires");
2976 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2977 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2979 switch (msg->response) {
2980 case 200:
2981 if (expires == 0) {
2982 sip->registerstatus = 0;
2983 } else {
2984 int i = 0;
2985 gchar *contact_hdr = NULL;
2986 gchar *gruu = NULL;
2987 gchar *epid;
2988 gchar *uuid;
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") && benotify)
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 void send_presence_soap(struct sipe_account_data *sip, const char * note)
3730 int availability = 300; // online
3731 int activity = 400; // Available
3732 gchar *name;
3733 gchar *body;
3734 if (!strcmp(sip->status, "away")) {
3735 activity = 100;
3736 } else if (!strcmp(sip->status, "out-to-lunch")) {
3737 activity = 150;
3738 } else if (!strcmp(sip->status, "be-right-back")) {
3739 activity = 300;
3740 } else if (!strcmp(sip->status, "on-the-phone")) {
3741 activity = 500;
3742 } else if (!strcmp(sip->status, "do-not-disturb")) {
3743 activity = 600;
3744 } else if (!strcmp(sip->status, "busy")) {
3745 activity = 600;
3746 } else if (!strcmp(sip->status, "invisible")) {
3747 availability = 0; // offline
3748 activity = 100;
3751 name = g_strdup_printf("sip: sip:%s", sip->username);
3752 //@TODO: send user data - state; add hostname in upper case
3753 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3754 send_soap_request_with_cb(sip, body, NULL , NULL);
3755 g_free(name);
3756 g_free(body);
3759 static gboolean
3760 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3762 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3763 if (msg->response == 200) {
3764 sip->status_version = 0;
3765 send_presence_status(sip);
3767 return TRUE;
3770 static gboolean
3771 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3773 if (msg->response == 409) {
3774 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3775 // TODO need to parse the version #'s?
3776 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3777 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3778 gchar *tmp;
3779 gchar *hdr;
3781 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
3783 tmp = get_contact(sip);
3784 hdr = g_strdup_printf("Contact: %s\r\n"
3785 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3787 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3789 g_free(tmp);
3790 g_free(hdr);
3791 g_free(uri);
3792 g_free(doc);
3794 return TRUE;
3797 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
3799 int code;
3800 gchar *uri;
3801 gchar *doc;
3802 gchar *tmp;
3803 gchar *hdr;
3804 if (!strcmp(sip->status, "away")) {
3805 code = 12000;
3806 } else if (!strcmp(sip->status, "busy")) {
3807 code = 6000;
3808 } else {
3809 // Available
3810 code = 3000;
3813 uri = g_strdup_printf("sip:%s", sip->username);
3814 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3815 sip->status_version, code,
3816 sip->status_version, code,
3817 sip->status_version, note ? note : "",
3818 sip->status_version, note ? note : "",
3819 sip->status_version, note ? note : ""
3821 sip->status_version++;
3823 tmp = get_contact(sip);
3824 hdr = g_strdup_printf("Contact: %s\r\n"
3825 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3827 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
3829 g_free(tmp);
3830 g_free(hdr);
3831 g_free(uri);
3832 g_free(doc);
3835 static void send_presence_status(struct sipe_account_data *sip)
3837 PurpleStatus * status = purple_account_get_active_status(sip->account);
3838 const gchar *note;
3839 if (!status) return;
3841 note = purple_status_get_attr_string(status, "message");
3843 if(sip->msrtc_event_categories){
3844 send_presence_category_publish(sip, note);
3845 } else {
3846 send_presence_soap(sip, note);
3850 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3852 gboolean found = FALSE;
3853 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3854 if (msg->response == 0) { /* request */
3855 if (!strcmp(msg->method, "MESSAGE")) {
3856 process_incoming_message(sip, msg);
3857 found = TRUE;
3858 } else if (!strcmp(msg->method, "NOTIFY")) {
3859 purple_debug_info("sipe","send->process_incoming_notify\n");
3860 process_incoming_notify(sip, msg, TRUE, FALSE);
3861 found = TRUE;
3862 } else if (!strcmp(msg->method, "BENOTIFY")) {
3863 purple_debug_info("sipe","send->process_incoming_benotify\n");
3864 process_incoming_notify(sip, msg, TRUE, TRUE);
3865 found = TRUE;
3866 } else if (!strcmp(msg->method, "INVITE")) {
3867 process_incoming_invite(sip, msg);
3868 found = TRUE;
3869 } else if (!strcmp(msg->method, "OPTIONS")) {
3870 process_incoming_options(sip, msg);
3871 found = TRUE;
3872 } else if (!strcmp(msg->method, "INFO")) {
3873 // TODO needs work
3874 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3875 if (from) {
3876 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3878 g_free(from);
3879 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3880 found = TRUE;
3881 } else if (!strcmp(msg->method, "ACK")) {
3882 // ACK's don't need any response
3883 found = TRUE;
3884 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3885 // LCS 2005 sends us these - just respond 200 OK
3886 found = TRUE;
3887 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3888 } else if (!strcmp(msg->method, "BYE")) {
3889 struct sip_im_session *session;
3890 gchar *from;
3891 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3893 from = parse_from(sipmsg_find_header(msg, "From"));
3894 session = find_im_session (sip, from);
3895 g_free(from);
3897 if (session) {
3898 // TODO Let the user know the other user left the conversation?
3899 im_session_destroy(sip, session);
3902 found = TRUE;
3903 } else {
3904 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3906 } else { /* response */
3907 struct transaction *trans = transactions_find(sip, msg);
3908 if (trans) {
3909 if (msg->response == 407) {
3910 gchar *resend, *auth, *ptmp;
3912 if (sip->proxy.retries > 30) return;
3913 sip->proxy.retries++;
3914 /* do proxy authentication */
3916 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3918 fill_auth(sip, ptmp, &sip->proxy);
3919 auth = auth_header(sip, &sip->proxy, trans->msg);
3920 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3921 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
3922 g_free(auth);
3923 resend = sipmsg_to_string(trans->msg);
3924 /* resend request */
3925 sendout_pkt(sip->gc, resend);
3926 g_free(resend);
3927 } else {
3928 if (msg->response == 100 || msg->response == 180) {
3929 /* ignore provisional response */
3930 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
3931 } else {
3932 sip->proxy.retries = 0;
3933 if (!strcmp(trans->msg->method, "REGISTER")) {
3934 if (msg->response == 401)
3936 sip->registrar.retries++;
3937 sip->registrar.expires = 0;
3939 else
3941 sip->registrar.retries = 0;
3943 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
3944 } else {
3945 if (msg->response == 401) {
3946 gchar *resend, *auth, *ptmp;
3948 if (sip->registrar.retries > 4) return;
3949 sip->registrar.retries++;
3951 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3952 ptmp = sipmsg_find_auth_header(msg, "NTLM");
3953 } else {
3954 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
3957 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
3959 fill_auth(sip, ptmp, &sip->registrar);
3960 auth = auth_header(sip, &sip->registrar, trans->msg);
3961 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3962 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
3964 //sipmsg_remove_header(trans->msg, "Authorization");
3965 //sipmsg_add_header(trans->msg, "Authorization", auth);
3966 g_free(auth);
3967 resend = sipmsg_to_string(trans->msg);
3968 /* resend request */
3969 sendout_pkt(sip->gc, resend);
3970 g_free(resend);
3974 if (trans->callback) {
3975 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
3976 /* call the callback to process response*/
3977 (trans->callback)(sip, msg, trans);
3979 /* Not sure if this is needed or what needs to be done
3980 but transactions seem to be removed prematurely so
3981 this only removes them if the response is 200 OK */
3982 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
3983 /*Has a bug and it's unneccesary*/
3984 /*transactions_remove(sip, trans);*/
3988 found = TRUE;
3989 } else {
3990 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
3993 if (!found) {
3994 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
3998 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4000 char *cur;
4001 char *dummy;
4002 struct sipmsg *msg;
4003 int restlen;
4004 cur = conn->inbuf;
4006 /* according to the RFC remove CRLF at the beginning */
4007 while (*cur == '\r' || *cur == '\n') {
4008 cur++;
4010 if (cur != conn->inbuf) {
4011 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4012 conn->inbufused = strlen(conn->inbuf);
4015 /* Received a full Header? */
4016 sip->processing_input = TRUE;
4017 while (sip->processing_input &&
4018 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4019 time_t currtime = time(NULL);
4020 cur += 2;
4021 cur[0] = '\0';
4022 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4023 msg = sipmsg_parse_header(conn->inbuf);
4024 cur[0] = '\r';
4025 cur += 2;
4026 restlen = conn->inbufused - (cur - conn->inbuf);
4027 if (restlen >= msg->bodylen) {
4028 dummy = g_malloc(msg->bodylen + 1);
4029 memcpy(dummy, cur, msg->bodylen);
4030 dummy[msg->bodylen] = '\0';
4031 msg->body = dummy;
4032 cur += msg->bodylen;
4033 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4034 conn->inbufused = strlen(conn->inbuf);
4035 } else {
4036 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4037 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4038 sipmsg_free(msg);
4039 return;
4042 /*if (msg->body) {
4043 purple_debug_info("sipe", "body:\n%s", msg->body);
4046 // Verify the signature before processing it
4047 if (sip->registrar.ntlm_key) {
4048 struct sipmsg_breakdown msgbd;
4049 gchar *signature_input_str;
4050 gchar *signature = NULL;
4051 gchar *rspauth;
4052 msgbd.msg = msg;
4053 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4054 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4055 if (signature_input_str != NULL) {
4056 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4058 g_free(signature_input_str);
4060 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4062 if (signature != NULL) {
4063 if (rspauth != NULL) {
4064 if (purple_ntlm_verify_signature (signature, rspauth)) {
4065 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4066 process_input_message(sip, msg);
4067 } else {
4068 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4069 purple_connection_error(sip->gc, _("Invalid message signature received"));
4070 sip->gc->wants_to_die = TRUE;
4072 } else if (msg->response == 401) {
4073 purple_connection_error(sip->gc, _("Wrong Password"));
4074 sip->gc->wants_to_die = TRUE;
4076 g_free(signature);
4079 g_free(rspauth);
4080 sipmsg_breakdown_free(&msgbd);
4081 } else {
4082 process_input_message(sip, msg);
4085 sipmsg_free(msg);
4089 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4091 PurpleConnection *gc = data;
4092 struct sipe_account_data *sip = gc->proto_data;
4093 struct sipmsg *msg;
4094 int len;
4095 time_t currtime;
4097 static char buffer[65536];
4098 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4099 buffer[len] = '\0';
4100 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4101 msg = sipmsg_parse_msg(buffer);
4102 if (msg) process_input_message(sip, msg);
4106 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4108 struct sipe_account_data *sip = gc->proto_data;
4109 PurpleSslConnection *gsc = sip->gsc;
4111 purple_debug_error("sipe", "%s",debug);
4112 purple_connection_error(gc, msg);
4114 /* Invalidate this connection. Next send will open a new one */
4115 if (gsc) {
4116 connection_remove(sip, gsc->fd);
4117 purple_ssl_close(gsc);
4119 sip->gsc = NULL;
4120 sip->fd = -1;
4123 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4125 PurpleConnection *gc = data;
4126 struct sipe_account_data *sip;
4127 struct sip_connection *conn;
4128 int readlen, len;
4129 gboolean firstread = TRUE;
4131 /* NOTE: This check *IS* necessary */
4132 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4133 purple_ssl_close(gsc);
4134 return;
4137 sip = gc->proto_data;
4138 conn = connection_find(sip, gsc->fd);
4139 if (conn == NULL) {
4140 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4141 gc->wants_to_die = TRUE;
4142 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4143 return;
4146 /* Read all available data from the SSL connection */
4147 do {
4148 /* Increase input buffer size as needed */
4149 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4150 conn->inbuflen += SIMPLE_BUF_INC;
4151 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4152 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4155 /* Try to read as much as there is space left in the buffer */
4156 readlen = conn->inbuflen - conn->inbufused - 1;
4157 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4159 if (len < 0 && errno == EAGAIN) {
4160 /* Try again later */
4161 return;
4162 } else if (len < 0) {
4163 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4164 return;
4165 } else if (firstread && (len == 0)) {
4166 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4167 return;
4170 conn->inbufused += len;
4171 firstread = FALSE;
4173 /* Equivalence indicates that there is possibly more data to read */
4174 } while (len == readlen);
4176 conn->inbuf[conn->inbufused] = '\0';
4177 process_input(sip, conn);
4181 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4183 PurpleConnection *gc = data;
4184 struct sipe_account_data *sip = gc->proto_data;
4185 int len;
4186 struct sip_connection *conn = connection_find(sip, source);
4187 if (!conn) {
4188 purple_debug_error("sipe", "Connection not found!\n");
4189 return;
4192 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4193 conn->inbuflen += SIMPLE_BUF_INC;
4194 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4197 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4199 if (len < 0 && errno == EAGAIN)
4200 return;
4201 else if (len <= 0) {
4202 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4203 connection_remove(sip, source);
4204 if (sip->fd == source) sip->fd = -1;
4205 return;
4208 conn->inbufused += len;
4209 conn->inbuf[conn->inbufused] = '\0';
4211 process_input(sip, conn);
4214 /* Callback for new connections on incoming TCP port */
4215 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4217 PurpleConnection *gc = data;
4218 struct sipe_account_data *sip = gc->proto_data;
4219 struct sip_connection *conn;
4221 int newfd = accept(source, NULL, NULL);
4223 conn = connection_create(sip, newfd);
4225 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4228 static void login_cb(gpointer data, gint source, const gchar *error_message)
4230 PurpleConnection *gc = data;
4231 struct sipe_account_data *sip;
4232 struct sip_connection *conn;
4234 if (!PURPLE_CONNECTION_IS_VALID(gc))
4236 if (source >= 0)
4237 close(source);
4238 return;
4241 if (source < 0) {
4242 purple_connection_error(gc, _("Could not connect"));
4243 return;
4246 sip = gc->proto_data;
4247 sip->fd = source;
4248 sip->last_keepalive = time(NULL);
4250 conn = connection_create(sip, source);
4252 do_register(sip);
4254 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4257 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4259 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4260 if (sip == NULL) return;
4262 do_register(sip);
4265 static guint sipe_ht_hash_nick(const char *nick)
4267 char *lc = g_utf8_strdown(nick, -1);
4268 guint bucket = g_str_hash(lc);
4269 g_free(lc);
4271 return bucket;
4274 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4276 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4279 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4281 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4283 sip->listen_data = NULL;
4285 if (listenfd == -1) {
4286 purple_connection_error(sip->gc, _("Could not create listen socket"));
4287 return;
4290 sip->fd = listenfd;
4292 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4293 sip->listenfd = sip->fd;
4295 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4297 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4298 do_register(sip);
4301 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4303 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4304 int addr_size;
4306 sip->query_data = NULL;
4308 if (!hosts || !hosts->data) {
4309 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4310 return;
4313 addr_size = GPOINTER_TO_INT(hosts->data);
4314 hosts = g_slist_remove(hosts, hosts->data);
4315 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4316 g_free(hosts->data);
4317 hosts = g_slist_remove(hosts, hosts->data);
4318 while (hosts) {
4319 hosts = g_slist_remove(hosts, hosts->data);
4320 g_free(hosts->data);
4321 hosts = g_slist_remove(hosts, hosts->data);
4324 /* create socket for incoming connections */
4325 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4326 sipe_udp_host_resolved_listen_cb, sip);
4327 if (sip->listen_data == NULL) {
4328 purple_connection_error(sip->gc, _("Could not create listen socket"));
4329 return;
4333 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4334 gpointer data)
4336 PurpleConnection *gc = data;
4337 struct sipe_account_data *sip;
4339 /* If the connection is already disconnected, we don't need to do anything else */
4340 if (!PURPLE_CONNECTION_IS_VALID(gc))
4341 return;
4343 sip = gc->proto_data;
4344 sip->fd = -1;
4345 sip->gsc = NULL;
4347 switch(error) {
4348 case PURPLE_SSL_CONNECT_FAILED:
4349 purple_connection_error(gc, _("Connection Failed"));
4350 break;
4351 case PURPLE_SSL_HANDSHAKE_FAILED:
4352 purple_connection_error(gc, _("SSL Handshake Failed"));
4353 break;
4354 case PURPLE_SSL_CERTIFICATE_INVALID:
4355 purple_connection_error(gc, _("SSL Certificate Invalid"));
4356 break;
4360 static void
4361 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4363 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4364 PurpleProxyConnectData *connect_data;
4366 sip->listen_data = NULL;
4368 sip->listenfd = listenfd;
4369 if (sip->listenfd == -1) {
4370 purple_connection_error(sip->gc, _("Could not create listen socket"));
4371 return;
4374 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4375 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4376 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4377 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4378 sipe_newconn_cb, sip->gc);
4379 purple_debug_info("sipe", "connecting to %s port %d\n",
4380 sip->realhostname, sip->realport);
4381 /* open tcp connection to the server */
4382 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4383 sip->realport, login_cb, sip->gc);
4385 if (connect_data == NULL) {
4386 purple_connection_error(sip->gc, _("Couldn't create socket"));
4391 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4393 PurpleAccount *account = sip->account;
4394 PurpleConnection *gc = sip->gc;
4396 if (purple_account_get_bool(account, "useport", FALSE)) {
4397 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4398 port = purple_account_get_int(account, "port", 0);
4399 } else {
4400 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4403 sip->realhostname = hostname;
4404 sip->realport = port;
4406 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4407 hostname, port);
4409 /* TODO: is there a good default grow size? */
4410 if (sip->transport != SIPE_TRANSPORT_UDP)
4411 sip->txbuf = purple_circ_buffer_new(0);
4413 if (sip->transport == SIPE_TRANSPORT_TLS) {
4414 /* SSL case */
4415 if (!purple_ssl_is_supported()) {
4416 gc->wants_to_die = TRUE;
4417 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4418 return;
4421 purple_debug_info("sipe", "using SSL\n");
4423 sip->gsc = purple_ssl_connect(account, hostname, port,
4424 login_cb_ssl, sipe_ssl_connect_failure, gc);
4425 if (sip->gsc == NULL) {
4426 purple_connection_error(gc, _("Could not create SSL context"));
4427 return;
4429 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4430 /* UDP case */
4431 purple_debug_info("sipe", "using UDP\n");
4433 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4434 if (sip->query_data == NULL) {
4435 purple_connection_error(gc, _("Could not resolve hostname"));
4437 } else {
4438 /* TCP case */
4439 purple_debug_info("sipe", "using TCP\n");
4440 /* create socket for incoming connections */
4441 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4442 sipe_tcp_connect_listen_cb, sip);
4443 if (sip->listen_data == NULL) {
4444 purple_connection_error(gc, _("Could not create listen socket"));
4445 return;
4450 /* Service list for autodection */
4451 static const struct sipe_service_data service_autodetect[] = {
4452 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4453 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4454 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4455 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4456 { NULL, NULL, 0 }
4459 /* Service list for SSL/TLS */
4460 static const struct sipe_service_data service_tls[] = {
4461 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4462 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4463 { NULL, NULL, 0 }
4466 /* Service list for TCP */
4467 static const struct sipe_service_data service_tcp[] = {
4468 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4469 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4470 { NULL, NULL, 0 }
4473 /* Service list for UDP */
4474 static const struct sipe_service_data service_udp[] = {
4475 { "sip", "udp", SIPE_TRANSPORT_UDP },
4476 { NULL, NULL, 0 }
4479 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4480 static void resolve_next_service(struct sipe_account_data *sip,
4481 const struct sipe_service_data *start)
4483 if (start) {
4484 sip->service_data = start;
4485 } else {
4486 sip->service_data++;
4487 if (sip->service_data->service == NULL) {
4488 gchar *hostname;
4489 /* Try connecting to the SIP hostname directly */
4490 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4491 if (sip->auto_transport) {
4492 // If SSL is supported, default to using it; OCS servers aren't configured
4493 // by default to accept TCP
4494 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4495 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4496 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4499 hostname = g_strdup(sip->sipdomain);
4500 create_connection(sip, hostname, 0);
4501 return;
4505 /* Try to resolve next service */
4506 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4507 sip->service_data->transport,
4508 sip->sipdomain,
4509 srvresolved, sip);
4512 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4514 struct sipe_account_data *sip = data;
4516 sip->srv_query_data = NULL;
4518 /* find the host to connect to */
4519 if (results) {
4520 gchar *hostname = g_strdup(resp->hostname);
4521 int port = resp->port;
4522 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4523 hostname, port);
4524 g_free(resp);
4526 sip->transport = sip->service_data->type;
4528 create_connection(sip, hostname, port);
4529 } else {
4530 resolve_next_service(sip, NULL);
4534 static void sipe_login(PurpleAccount *account)
4536 PurpleConnection *gc;
4537 struct sipe_account_data *sip;
4538 gchar **signinname_login, **userserver, **domain_user;
4539 const char *transport;
4541 const char *username = purple_account_get_username(account);
4542 gc = purple_account_get_connection(account);
4544 if (strpbrk(username, " \t\v\r\n") != NULL) {
4545 gc->wants_to_die = TRUE;
4546 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4547 return;
4550 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4551 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4552 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4553 sip->gc = gc;
4554 sip->account = account;
4555 sip->reregister_set = FALSE;
4556 sip->reauthenticate_set = FALSE;
4557 sip->subscribed = FALSE;
4558 sip->subscribed_buddies = FALSE;
4560 signinname_login = g_strsplit(username, ",", 2);
4562 userserver = g_strsplit(signinname_login[0], "@", 2);
4563 purple_connection_set_display_name(gc, userserver[0]);
4564 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4565 sip->sipdomain = g_strdup(userserver[1]);
4567 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4568 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4569 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4571 sip->password = g_strdup(purple_connection_get_password(gc));
4573 g_strfreev(userserver);
4574 g_strfreev(domain_user);
4575 g_strfreev(signinname_login);
4577 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4579 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4581 /* TODO: Set the status correctly. */
4582 sip->status = g_strdup("available");
4584 transport = purple_account_get_string(account, "transport", "auto");
4585 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4586 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4587 SIPE_TRANSPORT_UDP;
4589 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4590 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4591 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4592 } else if (strcmp(transport, "auto") == 0) {
4593 sip->auto_transport = TRUE;
4594 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4595 } else if (strcmp(transport, "tls") == 0) {
4596 resolve_next_service(sip, service_tls);
4597 } else if (strcmp(transport, "tcp") == 0) {
4598 resolve_next_service(sip, service_tcp);
4599 } else {
4600 resolve_next_service(sip, service_udp);
4604 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4606 connection_free_all(sip);
4608 if (sip->query_data != NULL)
4609 purple_dnsquery_destroy(sip->query_data);
4610 sip->query_data = NULL;
4612 if (sip->srv_query_data != NULL)
4613 purple_srv_cancel(sip->srv_query_data);
4614 sip->srv_query_data = NULL;
4616 if (sip->listen_data != NULL)
4617 purple_network_listen_cancel(sip->listen_data);
4618 sip->listen_data = NULL;
4620 if (sip->gsc != NULL)
4621 purple_ssl_close(sip->gsc);
4622 sip->gsc = NULL;
4624 sipe_auth_free(&sip->registrar);
4625 sipe_auth_free(&sip->proxy);
4627 if (sip->txbuf)
4628 purple_circ_buffer_destroy(sip->txbuf);
4629 sip->txbuf = NULL;
4631 g_free(sip->realhostname);
4632 sip->realhostname = NULL;
4634 if (sip->listenpa)
4635 purple_input_remove(sip->listenpa);
4636 sip->listenpa = 0;
4637 if (sip->tx_handler)
4638 purple_input_remove(sip->tx_handler);
4639 sip->tx_handler = 0;
4640 if (sip->resendtimeout)
4641 purple_timeout_remove(sip->resendtimeout);
4642 sip->resendtimeout = 0;
4643 if (sip->timeouts) {
4644 GSList *entry = sip->timeouts;
4645 while (entry) {
4646 struct scheduled_action *sched_action = entry->data;
4647 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4648 purple_timeout_remove(sched_action->timeout_handler);
4649 g_free(sched_action->payload);
4650 g_free(sched_action->name);
4651 g_free(sched_action);
4652 entry = entry->next;
4655 g_slist_free(sip->timeouts);
4657 g_slist_free(sip->allow_events);
4659 if (sip->contact)
4660 g_free(sip->contact);
4661 sip->contact = NULL;
4662 if (sip->regcallid)
4663 g_free(sip->regcallid);
4664 sip->regcallid = NULL;
4666 sip->fd = -1;
4667 sip->processing_input = FALSE;
4671 * A callback for g_hash_table_foreach_remove
4673 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4675 sipe_free_buddy(buddy);
4678 static void sipe_close(PurpleConnection *gc)
4680 struct sipe_account_data *sip = gc->proto_data;
4682 if (sip) {
4683 /* leave all conversations */
4684 im_session_close_all(sip);
4686 /* unregister */
4687 do_register_exp(sip, 0);
4689 sipe_connection_cleanup(sip);
4690 g_free(sip->sipdomain);
4691 g_free(sip->username);
4692 g_free(sip->password);
4693 g_free(sip->authdomain);
4694 g_free(sip->authuser);
4695 g_free(sip->status);
4697 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4698 g_hash_table_destroy(sip->buddies);
4700 g_free(gc->proto_data);
4701 gc->proto_data = NULL;
4704 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4706 PurpleAccount *acct = purple_connection_get_account(gc);
4707 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4708 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4709 if (conv == NULL)
4710 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4711 purple_conversation_present(conv);
4712 g_free(id);
4715 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4718 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4719 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4722 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4724 PurpleNotifySearchResults *results;
4725 PurpleNotifySearchColumn *column;
4726 xmlnode *searchResults;
4727 xmlnode *mrow;
4728 int match_count = 0;
4729 gboolean more = FALSE;
4730 gchar *secondary;
4732 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
4734 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4735 if (!searchResults) {
4736 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4737 return FALSE;
4740 results = purple_notify_searchresults_new();
4742 if (results == NULL) {
4743 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4744 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4746 xmlnode_free(searchResults);
4747 return FALSE;
4750 column = purple_notify_searchresults_column_new(_("User Name"));
4751 purple_notify_searchresults_column_add(results, column);
4753 column = purple_notify_searchresults_column_new(_("Name"));
4754 purple_notify_searchresults_column_add(results, column);
4756 column = purple_notify_searchresults_column_new(_("Company"));
4757 purple_notify_searchresults_column_add(results, column);
4759 column = purple_notify_searchresults_column_new(_("Country"));
4760 purple_notify_searchresults_column_add(results, column);
4762 column = purple_notify_searchresults_column_new(_("Email"));
4763 purple_notify_searchresults_column_add(results, column);
4765 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4766 GList *row = NULL;
4768 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4769 row = g_list_append(row, g_strdup(uri_parts[1]));
4770 g_strfreev(uri_parts);
4772 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4773 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4774 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4775 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4777 purple_notify_searchresults_row_add(results, row);
4778 match_count++;
4781 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4782 char *data = xmlnode_get_data_unescaped(mrow);
4783 more = (g_strcasecmp(data, "true") == 0);
4784 g_free(data);
4787 secondary = g_strdup_printf(
4788 dngettext(GETTEXT_PACKAGE,
4789 "Found %d contact%s:",
4790 "Found %d contacts%s:", match_count),
4791 match_count, more ? _(" (more matched your query)") : "");
4793 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4794 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4795 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4797 g_free(secondary);
4798 xmlnode_free(searchResults);
4799 return TRUE;
4802 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4804 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4805 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4806 unsigned i = 0;
4808 do {
4809 PurpleRequestField *field = entries->data;
4810 const char *id = purple_request_field_get_id(field);
4811 const char *value = purple_request_field_string_get_value(field);
4813 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
4815 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
4816 } while ((entries = g_list_next(entries)) != NULL);
4817 attrs[i] = NULL;
4819 if (i > 0) {
4820 gchar *query = g_strjoinv(NULL, attrs);
4821 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
4822 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
4823 send_soap_request_with_cb(gc->proto_data, body,
4824 (TransCallback) process_search_contact_response, NULL);
4825 g_free(body);
4826 g_free(query);
4829 g_strfreev(attrs);
4832 static void sipe_show_find_contact(PurplePluginAction *action)
4834 PurpleConnection *gc = (PurpleConnection *) action->context;
4835 PurpleRequestFields *fields;
4836 PurpleRequestFieldGroup *group;
4837 PurpleRequestField *field;
4839 fields = purple_request_fields_new();
4840 group = purple_request_field_group_new(NULL);
4841 purple_request_fields_add_group(fields, group);
4843 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4844 purple_request_field_group_add_field(group, field);
4845 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4846 purple_request_field_group_add_field(group, field);
4847 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4848 purple_request_field_group_add_field(group, field);
4849 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4850 purple_request_field_group_add_field(group, field);
4852 purple_request_fields(gc,
4853 _("Search"),
4854 _("Search for a Contact"),
4855 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4856 fields,
4857 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4858 _("_Cancel"), NULL,
4859 purple_connection_get_account(gc), NULL, NULL, gc);
4862 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4864 GList *menu = NULL;
4865 PurplePluginAction *act;
4867 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4868 menu = g_list_prepend(menu, act);
4870 menu = g_list_reverse(menu);
4872 return menu;
4875 static void dummy_permit_deny(PurpleConnection *gc)
4879 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4881 return TRUE;
4885 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4887 return TRUE;
4891 static char *sipe_status_text(PurpleBuddy *buddy)
4893 struct sipe_account_data *sip;
4894 struct sipe_buddy *sbuddy;
4895 char *text = NULL;
4897 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4898 if (sip) //happens on pidgin exit
4900 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4901 if (sbuddy && sbuddy->annotation)
4903 text = g_strdup(sbuddy->annotation);
4907 return text;
4910 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4912 const PurplePresence *presence = purple_buddy_get_presence(buddy);
4913 const PurpleStatus *status = purple_presence_get_active_status(presence);
4914 struct sipe_account_data *sip;
4915 struct sipe_buddy *sbuddy;
4916 char *annotation = NULL;
4918 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4919 if (sip) //happens on pidgin exit
4921 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4922 if (sbuddy)
4924 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
4928 //Layout
4929 if (purple_presence_is_online(presence))
4931 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
4934 if (annotation)
4936 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
4937 g_free(annotation);
4942 static GHashTable *
4943 sipe_get_account_text_table(PurpleAccount *account)
4945 GHashTable *table;
4946 table = g_hash_table_new(g_str_hash, g_str_equal);
4947 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
4948 return table;
4951 static PurpleBuddy *
4952 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
4954 PurpleBuddy *clone;
4955 const gchar *server_alias, *email;
4956 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
4958 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
4960 purple_blist_add_buddy(clone, NULL, group, NULL);
4962 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
4963 if (server_alias) {
4964 purple_blist_server_alias_buddy(clone, server_alias);
4967 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4968 if (email) {
4969 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
4972 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
4973 //for UI to update;
4974 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
4975 return clone;
4978 static void
4979 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
4981 PurpleBuddy *buddy, *b;
4982 PurpleConnection *gc;
4983 PurpleGroup * group = purple_find_group(group_name);
4985 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
4987 buddy = (PurpleBuddy *)node;
4989 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
4990 gc = purple_account_get_connection(buddy->account);
4992 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
4993 if (!b){
4994 b = purple_blist_add_buddy_clone(group, buddy);
4997 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5000 static void
5001 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5003 const gchar *email;
5004 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5006 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5007 if (email)
5009 char *mailto = g_strdup_printf("mailto:%s", email);
5010 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5011 #ifndef _WIN32
5013 pid_t pid;
5014 char *const parmList[] = {mailto, NULL};
5015 if ((pid = fork()) == -1)
5017 purple_debug_info("sipe", "fork() error\n");
5019 else if (pid == 0)
5021 execvp("xdg-email", parmList);
5022 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5025 #else
5027 BOOL ret;
5028 _flushall();
5029 errno = 0;
5030 //@TODO resolve env variable %WINDIR% first
5031 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5032 if (errno)
5034 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5037 #endif
5039 g_free(mailto);
5041 else
5043 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5048 * A menu which appear when right-clicking on buddy in contact list.
5050 static GList *
5051 sipe_buddy_menu(PurpleBuddy *buddy)
5053 PurpleBlistNode *g_node;
5054 PurpleGroup *group, *gr_parent;
5055 PurpleMenuAction *act;
5056 GList *menu = NULL;
5057 GList *menu_groups = NULL;
5059 act = purple_menu_action_new(_("Send Email..."),
5060 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5061 NULL, NULL);
5062 menu = g_list_prepend(menu, act);
5064 gr_parent = purple_buddy_get_group(buddy);
5065 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5066 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5067 continue;
5069 group = (PurpleGroup *)g_node;
5070 if (group == gr_parent)
5071 continue;
5073 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5074 continue;
5076 act = purple_menu_action_new(purple_group_get_name(group),
5077 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5078 group->name, NULL);
5079 menu_groups = g_list_prepend(menu_groups, act);
5081 menu_groups = g_list_reverse(menu_groups);
5083 act = purple_menu_action_new(_("Copy to"),
5084 NULL,
5085 NULL, menu_groups);
5086 menu = g_list_prepend(menu, act);
5087 menu = g_list_reverse(menu);
5089 return menu;
5092 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5093 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5094 return sipe_buddy_menu((PurpleBuddy *) node);
5095 } else {
5096 return NULL;
5100 static gboolean
5101 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5103 gboolean ret = TRUE;
5104 char *username = (char *)trans->payload;
5106 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5107 PurpleBuddy *pbuddy;
5108 struct sipe_buddy *sbuddy;
5109 const char *alias;
5110 char *server_alias = NULL;
5111 char *email = NULL;
5112 const char *device_name = NULL;
5114 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5116 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5117 alias = purple_buddy_get_local_alias(pbuddy);
5119 if (sip)
5121 //will query buddy UA's capabilities and send answer to log
5122 sipe_options_request(sip, username);
5124 sbuddy = g_hash_table_lookup(sip->buddies, username);
5125 if (sbuddy)
5127 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5131 if (msg->response != 200) {
5132 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5133 } else {
5134 xmlnode *searchResults;
5135 xmlnode *mrow;
5137 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5138 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5139 if (!searchResults) {
5140 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5141 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5142 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5143 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5144 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5145 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5146 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5147 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5148 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5149 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5150 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5151 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5152 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5153 if (!email || strcmp("", email)) {
5154 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5155 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5159 xmlnode_free(searchResults);
5162 purple_notify_user_info_add_section_break(info);
5164 if (!server_alias || !strcmp("", server_alias)) {
5165 g_free(server_alias);
5166 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5167 if (server_alias) {
5168 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5172 // same as server alias, do not present
5173 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5174 if (alias)
5176 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5179 if (!email || !strcmp("", email)) {
5180 g_free(email);
5181 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5182 if (email) {
5183 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5187 if (device_name)
5189 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5192 /* show a buddy's user info in a nice dialog box */
5193 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5194 username, /* buddy's username */
5195 info, /* body */
5196 NULL, /* callback called when dialog closed */
5197 NULL); /* userdata for callback */
5199 return ret;
5203 * AD search first, LDAP based
5205 static void sipe_get_info(PurpleConnection *gc, const char *username)
5207 char *row = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5208 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5210 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5211 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5212 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5213 g_free(body);
5214 g_free(row);
5217 static PurplePlugin *my_protocol = NULL;
5219 static PurplePluginProtocolInfo prpl_info =
5222 NULL, /* user_splits */
5223 NULL, /* protocol_options */
5224 NO_BUDDY_ICONS, /* icon_spec */
5225 sipe_list_icon, /* list_icon */
5226 NULL, /* list_emblems */
5227 sipe_status_text, /* status_text */
5228 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5229 sipe_status_types, /* away_states */
5230 sipe_blist_node_menu, /* blist_node_menu */
5231 NULL, /* chat_info */
5232 NULL, /* chat_info_defaults */
5233 sipe_login, /* login */
5234 sipe_close, /* close */
5235 sipe_im_send, /* send_im */
5236 NULL, /* set_info */ // TODO maybe
5237 sipe_send_typing, /* send_typing */
5238 sipe_get_info, /* get_info */
5239 sipe_set_status, /* set_status */
5240 NULL, /* set_idle */
5241 NULL, /* change_passwd */
5242 sipe_add_buddy, /* add_buddy */
5243 NULL, /* add_buddies */
5244 sipe_remove_buddy, /* remove_buddy */
5245 NULL, /* remove_buddies */
5246 sipe_add_permit, /* add_permit */
5247 sipe_add_deny, /* add_deny */
5248 sipe_add_deny, /* rem_permit */
5249 sipe_add_permit, /* rem_deny */
5250 dummy_permit_deny, /* set_permit_deny */
5251 NULL, /* join_chat */
5252 NULL, /* reject_chat */
5253 NULL, /* get_chat_name */
5254 NULL, /* chat_invite */
5255 NULL, /* chat_leave */
5256 NULL, /* chat_whisper */
5257 NULL, /* chat_send */
5258 sipe_keep_alive, /* keepalive */
5259 NULL, /* register_user */
5260 NULL, /* get_cb_info */ // deprecated
5261 NULL, /* get_cb_away */ // deprecated
5262 sipe_alias_buddy, /* alias_buddy */
5263 sipe_group_buddy, /* group_buddy */
5264 sipe_rename_group, /* rename_group */
5265 NULL, /* buddy_free */
5266 sipe_convo_closed, /* convo_closed */
5267 purple_normalize_nocase, /* normalize */
5268 NULL, /* set_buddy_icon */
5269 sipe_remove_group, /* remove_group */
5270 NULL, /* get_cb_real_name */ // TODO?
5271 NULL, /* set_chat_topic */
5272 NULL, /* find_blist_chat */
5273 NULL, /* roomlist_get_list */
5274 NULL, /* roomlist_cancel */
5275 NULL, /* roomlist_expand_category */
5276 NULL, /* can_receive_file */
5277 NULL, /* send_file */
5278 NULL, /* new_xfer */
5279 NULL, /* offline_message */
5280 NULL, /* whiteboard_prpl_ops */
5281 sipe_send_raw, /* send_raw */
5282 NULL, /* roomlist_room_serialize */
5283 NULL, /* unregister_user */
5284 NULL, /* send_attention */
5285 NULL, /* get_attention_types */
5287 sizeof(PurplePluginProtocolInfo), /* struct_size */
5288 sipe_get_account_text_table, /* get_account_text_table */
5292 static PurplePluginInfo info = {
5293 PURPLE_PLUGIN_MAGIC,
5294 PURPLE_MAJOR_VERSION,
5295 PURPLE_MINOR_VERSION,
5296 PURPLE_PLUGIN_PROTOCOL, /**< type */
5297 NULL, /**< ui_requirement */
5298 0, /**< flags */
5299 NULL, /**< dependencies */
5300 PURPLE_PRIORITY_DEFAULT, /**< priority */
5301 "prpl-sipe", /**< id */
5302 "Microsoft LCS/OCS", /**< name */
5303 VERSION, /**< version */
5304 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5305 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5306 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5307 "Gabriel Burt <gburt@novell.com>", /**< author */
5308 PURPLE_WEBSITE, /**< homepage */
5309 sipe_plugin_load, /**< load */
5310 sipe_plugin_unload, /**< unload */
5311 sipe_plugin_destroy, /**< destroy */
5312 NULL, /**< ui_info */
5313 &prpl_info, /**< extra_info */
5314 NULL,
5315 sipe_actions,
5316 NULL,
5317 NULL,
5318 NULL,
5319 NULL
5322 static void sipe_plugin_destroy(PurplePlugin *plugin)
5324 GList *entry;
5326 entry = prpl_info.protocol_options;
5327 while (entry) {
5328 purple_account_option_destroy(entry->data);
5329 entry = g_list_delete_link(entry, entry);
5331 prpl_info.protocol_options = NULL;
5333 entry = prpl_info.user_splits;
5334 while (entry) {
5335 purple_account_user_split_destroy(entry->data);
5336 entry = g_list_delete_link(entry, entry);
5338 prpl_info.user_splits = NULL;
5341 static void init_plugin(PurplePlugin *plugin)
5343 PurpleAccountUserSplit *split;
5344 PurpleAccountOption *option;
5346 #ifdef ENABLE_NLS
5347 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5348 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5349 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5350 #endif
5352 purple_plugin_register(plugin);
5354 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5355 purple_account_user_split_set_reverse(split, FALSE);
5356 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5358 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5359 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5360 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5361 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5363 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5364 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5365 // Translators: noun (networking port)
5366 option = purple_account_option_int_new(_("Port"), "port", 5061);
5367 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5369 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5370 purple_account_option_add_list_item(option, _("Auto"), "auto");
5371 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5372 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5373 purple_account_option_add_list_item(option, _("UDP"), "udp");
5374 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5376 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5377 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5379 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5380 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5382 // TODO commented out so won't show in the preferences until we fix krb message signing
5383 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5384 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5386 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5387 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5388 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5391 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5392 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5393 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5394 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5395 my_protocol = plugin;
5398 /* I had to redefined the function for it load, but works */
5399 gboolean purple_init_plugin(PurplePlugin *plugin){
5400 plugin->info = &(info);
5401 init_plugin((plugin));
5402 sipe_plugin_load((plugin));
5403 return purple_plugin_register(plugin);