Little fix when the uri inside a is empty like: <note><body ... uri=></note>
[siplcs.git] / src / sipe.c
blobe120b41d28566e5e4bdddf36bdc1b69bd699ec17
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_to_name_single(struct sipe_account_data *sip, const char * buddy_name);
143 static void sipe_subscribe_to_buddies_batched(struct sipe_account_data *sip);
144 static void send_presence_info(struct sipe_account_data *sip);
146 static void sendout_pkt(PurpleConnection *gc, const char *buf);
148 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
150 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
151 if (timeout != NULL) {
152 sscanf(timeout, "%u", &sip->keepalive_timeout);
153 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
154 sip->keepalive_timeout);
156 g_free(timeout);
159 static void sipe_keep_alive(PurpleConnection *gc)
161 struct sipe_account_data *sip = gc->proto_data;
162 if (sip->transport == SIPE_TRANSPORT_UDP) {
163 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
164 gchar buf[2] = {0, 0};
165 purple_debug_info("sipe", "sending keep alive\n");
166 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
167 } else {
168 time_t now = time(NULL);
169 if ((sip->keepalive_timeout > 0) &&
170 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
171 #if PURPLE_VERSION_CHECK(2,4,0)
172 && ((now - gc->last_received) >= sip->keepalive_timeout)
173 #endif
175 purple_debug_info("sipe", "sending keep alive\n");
176 sendout_pkt(gc, "\r\n\r\n");
177 sip->last_keepalive = now;
182 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
184 struct sip_connection *ret = NULL;
185 GSList *entry = sip->openconns;
186 while (entry) {
187 ret = entry->data;
188 if (ret->fd == fd) return ret;
189 entry = entry->next;
191 return NULL;
194 static void sipe_auth_free(struct sip_auth *auth)
196 g_free(auth->nonce);
197 auth->nonce = NULL;
198 g_free(auth->opaque);
199 auth->opaque = NULL;
200 g_free(auth->realm);
201 auth->realm = NULL;
202 g_free(auth->target);
203 auth->target = NULL;
204 g_free(auth->digest_session_key);
205 auth->digest_session_key = NULL;
206 g_free(auth->ntlm_key);
207 auth->ntlm_key = NULL;
208 auth->type = AUTH_TYPE_UNSET;
209 auth->retries = 0;
210 auth->expires = 0;
213 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
215 struct sip_connection *ret = g_new0(struct sip_connection, 1);
216 ret->fd = fd;
217 sip->openconns = g_slist_append(sip->openconns, ret);
218 return ret;
221 static void connection_remove(struct sipe_account_data *sip, int fd)
223 struct sip_connection *conn = connection_find(sip, fd);
224 if (conn) {
225 sip->openconns = g_slist_remove(sip->openconns, conn);
226 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
227 g_free(conn->inbuf);
228 g_free(conn);
232 static void connection_free_all(struct sipe_account_data *sip)
234 struct sip_connection *ret = NULL;
235 GSList *entry = sip->openconns;
236 while (entry) {
237 ret = entry->data;
238 connection_remove(sip, ret->fd);
239 entry = sip->openconns;
243 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
245 const gchar *method = msg->method;
246 const gchar *target = msg->target;
247 gchar noncecount[9];
248 gchar *response;
249 gchar *ret;
250 gchar *tmp = NULL;
251 const char *authdomain = sip->authdomain;
252 const char *authuser = sip->authuser;
253 //const char *krb5_realm;
254 const char *host;
255 //gchar *krb5_token = NULL;
257 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
258 // and do error checking
260 // KRB realm should always be uppercase
261 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
263 if (sip->realhostname) {
264 host = sip->realhostname;
265 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
266 host = purple_account_get_string(sip->account, "proxy", "");
267 } else {
268 host = sip->sipdomain;
271 /*gboolean new_auth = krb5_auth.gss_context == NULL;
272 if (new_auth) {
273 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
276 if (new_auth || force_reauth) {
277 krb5_token = krb5_auth.base64_token;
280 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
281 krb5_token = krb5_auth.base64_token;*/
283 if (!authdomain) {
284 authdomain = "";
287 if (!authuser || strlen(authuser) < 1) {
288 authuser = sip->username;
291 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
292 sprintf(noncecount, "%08d", auth->nc++);
293 response = purple_cipher_http_digest_calculate_response(
294 "md5", method, target, NULL, NULL,
295 auth->nonce, noncecount, NULL, auth->digest_session_key);
296 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
298 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
299 g_free(response);
300 return ret;
301 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
302 // If we have a signature for the message, include that
303 if (msg->signature) {
304 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
305 return tmp;
308 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
309 const gchar *ntlm_key;
310 gchar *gssapi_data;
311 #if GLIB_CHECK_VERSION(2,8,0)
312 const gchar * hostname = g_get_host_name();
313 #else
314 static char hostname[256];
315 int ret = gethostname(hostname, sizeof(hostname));
316 hostname[sizeof(hostname) - 1] = '\0';
317 if (ret == -1 || hostname[0] == '\0') {
318 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
319 g_strerror(errno);
320 strcpy(hostname, "localhost");
322 #endif
323 /*const gchar * hostname = purple_get_host_name();*/
325 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
326 auth->ntlm_key = (gchar *)ntlm_key;
327 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
328 g_free(gssapi_data);
329 return tmp;
332 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
333 return tmp;
334 } else if (auth->type == AUTH_TYPE_KERBEROS) {
335 /* Kerberos */
336 if (auth->nc == 3) {
337 /*if (new_auth || force_reauth) {
338 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
339 if (auth->opaque) {
340 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, krb5_token);
341 } else {
342 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
344 } else {
345 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
346 gchar * mic = "MICTODO";
347 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
348 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
349 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
350 //auth->opaque ? auth->opaque : "", auth->target);
351 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
352 //g_free(mic);
354 return tmp;
356 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
359 sprintf(noncecount, "%08d", auth->nc++);
360 response = purple_cipher_http_digest_calculate_response(
361 "md5", method, target, NULL, NULL,
362 auth->nonce, noncecount, NULL, auth->digest_session_key);
363 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
365 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
366 g_free(response);
367 return ret;
370 static char *parse_attribute(const char *attrname, const char *source)
372 const char *tmp, *tmp2;
373 char *retval = NULL;
374 int len = strlen(attrname);
376 if (!strncmp(source, attrname, len)) {
377 tmp = source + len;
378 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
379 if (tmp2)
380 retval = g_strndup(tmp, tmp2 - tmp);
381 else
382 retval = g_strdup(tmp);
385 return retval;
388 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
390 int i = 0;
391 const char *authuser;
392 char *tmp;
393 gchar **parts;
394 //const char *krb5_realm;
395 //const char *host;
397 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
398 // and do error checking
400 // KRB realm should always be uppercase
401 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
403 if (sip->realhostname) {
404 host = sip->realhostname;
405 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
406 host = purple_account_get_string(sip->account, "proxy", "");
407 } else {
408 host = sip->sipdomain;
411 authuser = sip->authuser;
413 if (!authuser || strlen(authuser) < 1) {
414 authuser = sip->username;
417 if (!hdr) {
418 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
419 return;
422 if (!g_strncasecmp(hdr, "NTLM", 4)) {
423 auth->type = AUTH_TYPE_NTLM;
424 parts = g_strsplit(hdr+5, "\", ", 0);
425 i = 0;
426 while (parts[i]) {
427 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
428 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
429 g_free(auth->nonce);
430 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
431 g_free(tmp);
433 if ((tmp = parse_attribute("targetname=\"",
434 parts[i]))) {
435 g_free(auth->target);
436 auth->target = tmp;
438 else if ((tmp = parse_attribute("realm=\"",
439 parts[i]))) {
440 g_free(auth->realm);
441 auth->realm = tmp;
443 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
444 g_free(auth->opaque);
445 auth->opaque = tmp;
447 i++;
449 g_strfreev(parts);
450 auth->nc = 1;
451 if (!strstr(hdr, "gssapi-data")) {
452 auth->nc = 1;
453 } else {
454 auth->nc = 3;
456 return;
459 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
460 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
461 auth->type = AUTH_TYPE_KERBEROS;
462 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
463 parts = g_strsplit(hdr+9, "\", ", 0);
464 i = 0;
465 while (parts[i]) {
466 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
467 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
468 /*if (krb5_auth.gss_context == NULL) {
469 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
471 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
472 g_free(tmp);
474 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
475 g_free(auth->target);
476 auth->target = tmp;
477 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
478 g_free(auth->realm);
479 auth->realm = tmp;
480 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
481 g_free(auth->opaque);
482 auth->opaque = tmp;
484 i++;
486 g_strfreev(parts);
487 auth->nc = 3;
488 return;
491 auth->type = AUTH_TYPE_DIGEST;
492 parts = g_strsplit(hdr, " ", 0);
493 while (parts[i]) {
494 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
495 g_free(auth->nonce);
496 auth->nonce = tmp;
498 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
499 g_free(auth->realm);
500 auth->realm = tmp;
502 i++;
504 g_strfreev(parts);
506 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
507 if (auth->realm) {
508 g_free(auth->digest_session_key);
509 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
510 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
512 auth->nc = 1;
516 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
518 PurpleConnection *gc = data;
519 struct sipe_account_data *sip = gc->proto_data;
520 gsize max_write;
521 gssize written;
523 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
525 if (max_write == 0) {
526 if (sip->tx_handler != 0){
527 purple_input_remove(sip->tx_handler);
528 sip->tx_handler = 0;
530 return;
533 written = write(sip->fd, sip->txbuf->outptr, max_write);
535 if (written < 0 && errno == EAGAIN)
536 written = 0;
537 else if (written <= 0) {
538 /*TODO: do we really want to disconnect on a failure to write?*/
539 purple_connection_error(gc, _("Could not write"));
540 return;
543 purple_circ_buffer_mark_read(sip->txbuf, written);
546 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
548 PurpleConnection *gc = data;
549 struct sipe_account_data *sip = gc->proto_data;
550 gsize max_write;
551 gssize written;
553 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
555 if (max_write == 0) {
556 if (sip->tx_handler != 0) {
557 purple_input_remove(sip->tx_handler);
558 sip->tx_handler = 0;
559 return;
563 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
565 if (written < 0 && errno == EAGAIN)
566 written = 0;
567 else if (written <= 0) {
568 /*TODO: do we really want to disconnect on a failure to write?*/
569 purple_connection_error(gc, _("Could not write"));
570 return;
573 purple_circ_buffer_mark_read(sip->txbuf, written);
576 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
578 static void send_later_cb(gpointer data, gint source, const gchar *error)
580 PurpleConnection *gc = data;
581 struct sipe_account_data *sip;
582 struct sip_connection *conn;
584 if (!PURPLE_CONNECTION_IS_VALID(gc))
586 if (source >= 0)
587 close(source);
588 return;
591 if (source < 0) {
592 purple_connection_error(gc, _("Could not connect"));
593 return;
596 sip = gc->proto_data;
597 sip->fd = source;
598 sip->connecting = FALSE;
599 sip->last_keepalive = time(NULL);
601 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
603 /* If there is more to write now, we need to register a handler */
604 if (sip->txbuf->bufused > 0)
605 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
607 conn = connection_create(sip, source);
608 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
611 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
613 struct sipe_account_data *sip;
614 struct sip_connection *conn;
616 if (!PURPLE_CONNECTION_IS_VALID(gc))
618 if (gsc) purple_ssl_close(gsc);
619 return NULL;
622 sip = gc->proto_data;
623 sip->fd = gsc->fd;
624 sip->gsc = gsc;
625 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
626 sip->connecting = FALSE;
627 sip->last_keepalive = time(NULL);
629 conn = connection_create(sip, gsc->fd);
631 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
633 return sip;
636 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
638 PurpleConnection *gc = data;
639 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
640 if (sip == NULL) return;
642 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
644 /* If there is more to write now */
645 if (sip->txbuf->bufused > 0) {
646 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
651 static void sendlater(PurpleConnection *gc, const char *buf)
653 struct sipe_account_data *sip = gc->proto_data;
655 if (!sip->connecting) {
656 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
657 if (sip->transport == SIPE_TRANSPORT_TLS){
658 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
659 } else {
660 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
661 purple_connection_error(gc, _("Couldn't create socket"));
664 sip->connecting = TRUE;
667 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
668 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
670 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
673 static void sendout_pkt(PurpleConnection *gc, const char *buf)
675 struct sipe_account_data *sip = gc->proto_data;
676 time_t currtime = time(NULL);
677 int writelen = strlen(buf);
679 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
680 if (sip->transport == SIPE_TRANSPORT_UDP) {
681 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
682 purple_debug_info("sipe", "could not send packet\n");
684 } else {
685 int ret;
686 if (sip->fd < 0) {
687 sendlater(gc, buf);
688 return;
691 if (sip->tx_handler) {
692 ret = -1;
693 errno = EAGAIN;
694 } else{
695 if (sip->gsc){
696 ret = purple_ssl_write(sip->gsc, buf, writelen);
697 }else{
698 ret = write(sip->fd, buf, writelen);
702 if (ret < 0 && errno == EAGAIN)
703 ret = 0;
704 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
705 sendlater(gc, buf);
706 return;
709 if (ret < writelen) {
710 if (!sip->tx_handler){
711 if (sip->gsc){
712 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
714 else{
715 sip->tx_handler = purple_input_add(sip->fd,
716 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
717 gc);
721 /* XXX: is it OK to do this? You might get part of a request sent
722 with part of another. */
723 if (sip->txbuf->bufused > 0)
724 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
726 purple_circ_buffer_append(sip->txbuf, buf + ret,
727 writelen - ret);
732 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
734 sendout_pkt(gc, buf);
735 return len;
738 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
740 GSList *tmp = msg->headers;
741 gchar *name;
742 gchar *value;
743 GString *outstr = g_string_new("");
744 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
745 while (tmp) {
746 name = ((struct siphdrelement*) (tmp->data))->name;
747 value = ((struct siphdrelement*) (tmp->data))->value;
748 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
749 tmp = g_slist_next(tmp);
751 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
752 sendout_pkt(sip->gc, outstr->str);
753 g_string_free(outstr, TRUE);
756 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
758 gchar * buf;
759 if (sip->registrar.ntlm_key) {
760 struct sipmsg_breakdown msgbd;
761 gchar *signature_input_str;
762 msgbd.msg = msg;
763 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
764 msgbd.rand = g_strdup_printf("%08x", g_random_int());
765 sip->registrar.ntlm_num++;
766 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
767 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
768 if (signature_input_str != NULL) {
769 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
770 msg->rand = g_strdup(msgbd.rand);
771 msg->num = g_strdup(msgbd.num);
772 g_free(signature_input_str);
774 sipmsg_breakdown_free(&msgbd);
777 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
778 buf = auth_header(sip, &sip->registrar, msg);
779 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
780 sipmsg_add_header(msg, "Authorization", buf);
781 } else {
782 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
784 g_free(buf);
785 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS")) {
786 sip->registrar.nc = 3;
787 sip->registrar.type = AUTH_TYPE_NTLM;
789 buf = auth_header(sip, &sip->registrar, msg);
790 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
791 g_free(buf);
792 } else {
793 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
797 static char *get_contact(struct sipe_account_data *sip)
799 return g_strdup(sip->contact);
803 * unused. Needed?
804 static char *get_contact_service(struct sipe_account_data *sip)
806 return g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, generateUUIDfromEPID(get_epid()));
807 //return g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
811 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
812 const char *text, const char *body)
814 gchar *name;
815 gchar *value;
816 GString *outstr = g_string_new("");
817 struct sipe_account_data *sip = gc->proto_data;
818 gchar *contact;
819 GSList *tmp;
821 sipmsg_remove_header(msg, "ms-user-data");
823 contact = get_contact(sip);
824 sipmsg_remove_header(msg, "Contact");
825 sipmsg_add_header(msg, "Contact", contact);
826 g_free(contact);
828 /* When sending the acknowlegements and errors, the content length from the original
829 message is still here, but there is no body; we need to make sure we're sending the
830 correct content length */
831 sipmsg_remove_header(msg, "Content-Length");
832 if (body) {
833 gchar len[12];
834 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
835 sipmsg_add_header(msg, "Content-Length", len);
836 } else {
837 sipmsg_remove_header(msg, "Content-Type");
838 sipmsg_add_header(msg, "Content-Length", "0");
841 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
842 //gchar * mic = "MICTODO";
843 msg->response = code;
845 sipmsg_remove_header(msg, "Authentication-Info");
846 sign_outgoing_message(msg, sip, msg->method);
848 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
849 tmp = msg->headers;
850 while (tmp) {
851 name = ((struct siphdrelement*) (tmp->data))->name;
852 value = ((struct siphdrelement*) (tmp->data))->value;
854 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
855 tmp = g_slist_next(tmp);
857 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
858 sendout_pkt(gc, outstr->str);
859 g_string_free(outstr, TRUE);
862 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
864 if (trans->msg) sipmsg_free(trans->msg);
865 sip->transactions = g_slist_remove(sip->transactions, trans);
866 g_free(trans);
869 static struct transaction *
870 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
872 struct transaction *trans = g_new0(struct transaction, 1);
873 trans->time = time(NULL);
874 trans->msg = (struct sipmsg *)msg;
875 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
876 trans->callback = callback;
877 sip->transactions = g_slist_append(sip->transactions, trans);
878 return trans;
881 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
883 struct transaction *trans;
884 GSList *transactions = sip->transactions;
885 gchar *cseq = sipmsg_find_header(msg, "CSeq");
887 while (transactions) {
888 trans = transactions->data;
889 if (!strcmp(trans->cseq, cseq)) {
890 return trans;
892 transactions = transactions->next;
895 return NULL;
898 static struct transaction *
899 send_sip_request(PurpleConnection *gc, const gchar *method,
900 const gchar *url, const gchar *to, const gchar *addheaders,
901 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
903 struct sipe_account_data *sip = gc->proto_data;
904 const char *addh = "";
905 char *buf;
906 struct sipmsg *msg;
907 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
908 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
909 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
910 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
911 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
912 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
913 gchar *route = strdup("");
914 gchar *epid = get_epid(); // TODO generate one per account/login
915 struct transaction *trans;
917 if (dialog && dialog->routes)
919 GSList *iter = dialog->routes;
921 while(iter)
923 char *tmp = route;
924 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
925 g_free(tmp);
926 iter = g_slist_next(iter);
930 if (!ourtag && !dialog) {
931 ourtag = gentag();
934 if (!strcmp(method, "REGISTER")) {
935 if (sip->regcallid) {
936 g_free(callid);
937 callid = g_strdup(sip->regcallid);
938 } else {
939 sip->regcallid = g_strdup(callid);
943 if (addheaders) addh = addheaders;
945 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
946 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
947 "From: <sip:%s>%s%s;epid=%s\r\n"
948 "To: <%s>%s%s%s%s\r\n"
949 "Max-Forwards: 70\r\n"
950 "CSeq: %d %s\r\n"
951 "User-Agent: %s\r\n"
952 "Call-ID: %s\r\n"
953 "%s%s"
954 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
955 method,
956 dialog && dialog->request ? dialog->request : url,
957 TRANSPORT_DESCRIPTOR,
958 purple_network_get_my_ip(-1),
959 sip->listenport,
960 branch ? ";branch=" : "",
961 branch ? branch : "",
962 sip->username,
963 ourtag ? ";tag=" : "",
964 ourtag ? ourtag : "",
965 epid,
967 theirtag ? ";tag=" : "",
968 theirtag ? theirtag : "",
969 theirepid ? ";epid=" : "",
970 theirepid ? theirepid : "",
971 dialog ? ++dialog->cseq : ++sip->cseq,
972 method,
973 useragent,
974 callid,
975 route,
976 addh,
977 body ? strlen(body) : 0,
978 body ? body : "");
981 //printf ("parsing msg buf:\n%s\n\n", buf);
982 msg = sipmsg_parse_msg(buf);
984 g_free(buf);
985 g_free(ourtag);
986 g_free(theirtag);
987 g_free(theirepid);
988 g_free(branch);
989 g_free(callid);
990 g_free(route);
991 g_free(epid);
993 sign_outgoing_message (msg, sip, method);
995 buf = sipmsg_to_string (msg);
997 /* add to ongoing transactions */
998 trans = transactions_add_buf(sip, msg, tc);
999 sendout_pkt(gc, buf);
1000 g_free(buf);
1002 return trans;
1005 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1007 gchar *from = g_strdup_printf("sip:%s", sip->username);
1008 gchar *contact = get_contact(sip);
1009 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1010 "Content-Type: application/SOAP+xml\r\n",contact);
1012 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1013 tr->payload = payload;
1015 g_free(from);
1016 g_free(contact);
1017 g_free(hdr);
1020 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1022 send_soap_request_with_cb(sip, body, NULL, NULL);
1025 static char *get_contact_register(struct sipe_account_data *sip)
1027 char *epid = get_epid();
1028 char *uuid = generateUUIDfromEPID(epid);
1029 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1030 g_free(uuid);
1031 g_free(epid);
1032 return(buf);
1035 static void do_register_exp(struct sipe_account_data *sip, int expire)
1037 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1038 char *to = g_strdup_printf("sip:%s", sip->username);
1039 char *contact = get_contact_register(sip);
1040 char *hdr = g_strdup_printf("Contact: %s\r\n"
1041 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1042 "Event: registration\r\n"
1043 "Allow-Events: presence\r\n"
1044 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1045 "Expires: %d\r\n", contact,expire);
1046 g_free(contact);
1048 sip->registerstatus = 1;
1050 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1051 process_register_response);
1053 g_free(hdr);
1054 g_free(uri);
1055 g_free(to);
1058 static void do_register_cb(struct sipe_account_data *sip)
1060 do_register_exp(sip, sip->registerexpire);
1061 sip->reregister_set = FALSE;
1064 static void do_register(struct sipe_account_data *sip)
1066 do_register_exp(sip, sip->registerexpire);
1070 * Returns URI from provided To or From header.
1072 * Needs to g_free() after use.
1074 * @return URI with sip: prefix
1076 static gchar *parse_from(const gchar *hdr)
1078 gchar *from;
1079 const gchar *tmp, *tmp2 = hdr;
1081 if (!hdr) return NULL;
1082 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1083 tmp = strchr(hdr, '<');
1085 /* i hate the different SIP UA behaviours... */
1086 if (tmp) { /* sip address in <...> */
1087 tmp2 = tmp + 1;
1088 tmp = strchr(tmp2, '>');
1089 if (tmp) {
1090 from = g_strndup(tmp2, tmp - tmp2);
1091 } else {
1092 purple_debug_info("sipe", "found < without > in From\n");
1093 return NULL;
1095 } else {
1096 tmp = strchr(tmp2, ';');
1097 if (tmp) {
1098 from = g_strndup(tmp2, tmp - tmp2);
1099 } else {
1100 from = g_strdup(tmp2);
1103 purple_debug_info("sipe", "got %s\n", from);
1104 return from;
1107 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1109 va_list args;
1110 xmlnode * node = NULL;
1111 const gchar * name;
1113 va_start(args, parent);
1114 while ((name = va_arg(args, const char *)) != NULL) {
1115 node = xmlnode_get_child(parent, name);
1116 if (node == NULL) return NULL;
1117 parent = node;
1119 va_end(args);
1121 return node;
1125 static void
1126 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1128 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1129 send_soap_request(sip, body);
1130 g_free(body);
1133 static void
1134 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1136 if (allow) {
1137 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1138 } else {
1139 purple_debug_info("sipe", "Blocking contact %s\n", who);
1142 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1145 static
1146 void sipe_auth_user_cb(void * data)
1148 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1149 if (!job) return;
1151 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1152 g_free(job);
1155 static
1156 void sipe_deny_user_cb(void * data)
1158 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1159 if (!job) return;
1161 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1162 g_free(job);
1165 static void
1166 sipe_add_permit(PurpleConnection *gc, const char *name)
1168 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1169 sipe_contact_allow_deny(sip, name, TRUE);
1172 static void
1173 sipe_add_deny(PurpleConnection *gc, const char *name)
1175 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1176 sipe_contact_allow_deny(sip, name, FALSE);
1179 /*static void
1180 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1182 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1183 sipe_contact_set_acl(sip, name, "");
1186 static void
1187 sipe_process_incoming_pending (struct sipe_account_data *sip, struct sipmsg * msg)
1189 xmlnode *watchers;
1190 xmlnode *watcher;
1191 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1192 if (msg->response != 0 && msg->response != 200) return;
1194 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1196 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1197 if (!watchers) return;
1199 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1200 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1201 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1202 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1204 // TODO pull out optional displayName to pass as alias
1205 if (remote_user) {
1206 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1207 job->who = remote_user;
1208 job->sip = sip;
1209 purple_account_request_authorization(
1210 sip->account,
1211 remote_user,
1212 NULL, // id
1213 alias,
1214 NULL, // message
1215 on_list,
1216 sipe_auth_user_cb,
1217 sipe_deny_user_cb,
1218 (void *) job);
1223 xmlnode_free(watchers);
1224 return;
1227 static void
1228 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1230 PurpleGroup * purple_group = purple_find_group(group->name);
1231 if (!purple_group) {
1232 purple_group = purple_group_new(group->name);
1233 purple_blist_add_group(purple_group, NULL);
1236 if (purple_group) {
1237 group->purple_group = purple_group;
1238 sip->groups = g_slist_append(sip->groups, group);
1239 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1240 } else {
1241 purple_debug_info("sipe", "did not add group %s\n", group->name);
1245 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1247 struct sipe_group *group;
1248 GSList *entry;
1249 if (sip == NULL) {
1250 return NULL;
1253 entry = sip->groups;
1254 while (entry) {
1255 group = entry->data;
1256 if (group->id == id) {
1257 return group;
1259 entry = entry->next;
1261 return NULL;
1264 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1266 struct sipe_group *group;
1267 GSList *entry;
1268 if (sip == NULL) {
1269 return NULL;
1272 entry = sip->groups;
1273 while (entry) {
1274 group = entry->data;
1275 if (!strcmp(group->name, name)) {
1276 return group;
1278 entry = entry->next;
1280 return NULL;
1283 static void
1284 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1286 gchar *body;
1287 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1288 body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1289 send_soap_request(sip, body);
1290 g_free(body);
1291 g_free(group->name);
1292 group->name = g_strdup(name);
1296 * Only appends if no such value already stored.
1297 * Like Set in Java.
1299 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1300 GSList * res = list;
1301 if (!g_slist_find_custom(list, data, func)) {
1302 res = g_slist_insert_sorted(list, data, func);
1304 return res;
1307 static int
1308 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1309 return group1->id - group2->id;
1313 * Returns string like "2 4 7 8" - group ids buddy belong to.
1315 static gchar *
1316 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1317 int i = 0;
1318 gchar *res;
1319 //creating array from GList, converting int to gchar*
1320 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1321 GSList *entry = buddy->groups;
1322 while (entry) {
1323 struct sipe_group * group = entry->data;
1324 ids_arr[i] = g_strdup_printf("%d", group->id);
1325 entry = entry->next;
1326 i++;
1328 ids_arr[i] = NULL;
1329 res = g_strjoinv(" ", ids_arr);
1330 g_strfreev(ids_arr);
1331 return res;
1335 * Sends buddy update to server
1337 static void
1338 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1340 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1341 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1343 if (buddy && purple_buddy) {
1344 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1345 gchar *body;
1346 gchar *groups = sipe_get_buddy_groups_string(buddy);
1347 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1349 body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1350 alias, groups, "true", buddy->name, sip->contacts_delta++
1352 send_soap_request(sip, body);
1353 g_free(groups);
1354 g_free(body);
1358 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1360 if (msg->response == 200) {
1361 struct sipe_group *group;
1362 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1363 xmlnode *xml;
1364 xmlnode *node;
1365 char *group_id;
1366 struct sipe_buddy *buddy;
1367 group->name = ctx->group_name;
1369 xml = xmlnode_from_str(msg->body, msg->bodylen);
1370 if (!xml) {
1371 g_free(ctx);
1372 return FALSE;
1375 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1376 if (!node) {
1377 g_free(ctx);
1378 xmlnode_free(xml);
1379 return FALSE;
1382 group_id = xmlnode_get_data(node);
1383 if (!group_id) {
1384 g_free(ctx);
1385 xmlnode_free(xml);
1386 return FALSE;
1389 group = g_new0(struct sipe_group, 1);
1390 group->id = (int)g_ascii_strtod(group_id, NULL);
1391 g_free(group_id);
1393 sipe_group_add(sip, group);
1395 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1396 if (buddy) {
1397 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1400 sipe_group_set_user(sip, ctx->user_name);
1402 g_free(ctx);
1403 xmlnode_free(xml);
1404 return TRUE;
1406 return FALSE;
1409 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1411 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1412 gchar *body;
1413 ctx->group_name = g_strdup(name);
1414 ctx->user_name = g_strdup(who);
1416 body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1417 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1418 g_free(body);
1422 * A timer callback
1423 * Should return FALSE if repetitive action is not needed
1425 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1427 gboolean ret;
1428 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1429 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1430 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1431 (sched_action->action)(sched_action->sip, sched_action->payload);
1432 ret = sched_action->repetitive;
1433 g_free(sched_action->payload);
1434 g_free(sched_action->name);
1435 g_free(sched_action);
1436 return ret;
1440 * Do schedule action for execution in the future.
1441 * Non repetitive execution.
1443 * @param name of action (will be copied)
1444 * @param timeout in seconds
1445 * @action callback function
1446 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1448 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1450 struct scheduled_action *sched_action;
1452 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1453 sched_action = g_new0(struct scheduled_action, 1);
1454 sched_action->repetitive = FALSE;
1455 sched_action->name = g_strdup(name);
1456 sched_action->action = action;
1457 sched_action->sip = sip;
1458 sched_action->payload = payload;
1459 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1460 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1461 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1465 * Kills action timer effectively cancelling
1466 * scheduled action
1468 * @param name of action
1470 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1472 GSList *entry;
1474 if (!sip->timeouts || !name) return;
1476 entry = sip->timeouts;
1477 while (entry) {
1478 struct scheduled_action *sched_action = entry->data;
1479 if(!strcmp(sched_action->name, name)) {
1480 GSList *to_delete = entry;
1481 entry = entry->next;
1482 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1483 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1484 purple_timeout_remove(sched_action->timeout_handler);
1485 g_free(sched_action->payload);
1486 g_free(sched_action->name);
1487 g_free(sched_action);
1488 } else {
1489 entry = entry->next;
1494 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1496 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1498 gchar *to;
1499 //purple_debug_info("sipe","process_subscribe_response: body:\n%s\n", msg->body);
1501 if (msg->response == 200 || msg->response == 202)
1503 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1505 process_incoming_notify(sip, msg, FALSE, FALSE);
1507 return TRUE;
1510 /* we can not subscribe -> user is offline (TODO unknown status?) */
1511 to = parse_from(sipmsg_find_header(tc->msg, "To")); /* can't be NULL since it is our own msg */
1512 purple_prpl_got_user_status(sip->account, to, "offline", NULL);
1513 g_free(to);
1514 return TRUE;
1517 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1519 gchar *tmp = *resources_uri;
1520 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1521 g_free(tmp);
1525 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1526 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1527 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1528 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1529 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1532 static void sipe_subscribe_to_buddies_batched(struct sipe_account_data *sip){
1533 gchar *to = g_strdup_printf("sip:%s", sip->username);
1534 gchar *contact = get_contact(sip);
1535 gchar *request;
1536 gchar *content;
1537 gchar *resources_uri = g_strdup("");
1538 gchar *require = "";
1539 gchar *accept = "";
1540 gchar *content_type;
1542 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1544 if (sip->msrtc_event_categories) {
1545 require = ", categoryList";
1546 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1547 content_type = "application/msrtc-adrl-categorylist+xml";
1548 content = g_strdup_printf(
1549 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1550 "<action name=\"subscribe\" id=\"63792024\">\n"
1551 "<adhocList>\n%s</adhocList>\n"
1552 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1553 "<category name=\"note\"/>\n"
1554 "<category name=\"state\"/>\n"
1555 "</categoryList>\n"
1556 "</action>\n"
1557 "</batchSub>", sip->username, resources_uri);
1558 } else {
1559 content_type = "application/adrl+xml";
1560 content = g_strdup_printf(
1561 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1562 "<create xmlns=\"\">\n%s</create>\n"
1563 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1565 g_free(resources_uri);
1567 request = g_strdup_printf(
1568 "Require: adhoclist%s\r\n"
1569 "Supported: eventlist\r\n"
1570 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1571 "Supported: ms-piggyback-first-notify\r\n"
1572 "Supported: com.microsoft.autoextend\r\n"
1573 "Supported: ms-benotify\r\n"
1574 "Proxy-Require: ms-benotify\r\n"
1575 "Event: presence\r\n"
1576 "Content-Type: %s\r\n"
1577 "Contact: %s\r\n", require, accept, content_type, contact);
1578 g_free(contact);
1580 /* subscribe to buddy presence */
1581 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1582 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1584 g_free(content);
1585 g_free(to);
1586 g_free(request);
1590 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1591 * The user sends a single SUBSCRIBE request to the subscribed contact.
1592 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1596 static void sipe_subscribe_to_name_single(struct sipe_account_data *sip, const char * buddy_name)
1598 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1599 gchar *tmp = get_contact(sip);
1600 gchar *request;
1601 gchar *content;
1602 request = g_strdup_printf(
1603 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1604 "Supported: ms-piggyback-first-notify\r\n"
1605 "Supported: com.microsoft.autoextend\r\n"
1606 "Supported: ms-benotify\r\n"
1607 "Proxy-Require: ms-benotify\r\n"
1608 "Event: presence\r\n"
1609 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1610 "Contact: %s\r\n", tmp);
1612 content = g_strdup_printf(
1613 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1614 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1615 "<resource uri=\"%s\"/>\n"
1616 "</adhocList>\n"
1617 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1618 "<category name=\"note\"/>\n"
1619 "<category name=\"state\"/>\n"
1620 "</categoryList>\n"
1621 "</action>\n"
1622 "</batchSub>", sip->username, to
1625 g_free(tmp);
1627 /* subscribe to buddy presence */
1628 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1630 g_free(content);
1631 g_free(to);
1632 g_free(request);
1635 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1637 if (!purple_status_is_active(status))
1638 return;
1640 if (account->gc) {
1641 struct sipe_account_data *sip = account->gc->proto_data;
1643 if (sip) {
1644 g_free(sip->status);
1645 sip->status = g_strdup(purple_status_get_id(status));
1646 send_presence_info(sip);
1651 static void
1652 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1654 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1655 sipe_group_set_user(sip, name);
1658 static void
1659 sipe_group_buddy(PurpleConnection *gc,
1660 const char *who,
1661 const char *old_group_name,
1662 const char *new_group_name)
1664 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1665 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1666 struct sipe_group * old_group = NULL;
1667 struct sipe_group * new_group;
1669 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1670 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1672 if(!buddy) { // buddy not in roaming list
1673 return;
1676 if (old_group_name) {
1677 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1679 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1681 if (old_group) {
1682 buddy->groups = g_slist_remove(buddy->groups, old_group);
1683 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1686 if (!new_group) {
1687 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1688 } else {
1689 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1690 sipe_group_set_user(sip, who);
1694 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1696 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1697 struct sipe_buddy *b;
1699 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1701 // Prepend sip: if needed
1702 if (strncmp("sip:", buddy->name, 4)) {
1703 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1704 purple_blist_rename_buddy(buddy, buf);
1705 g_free(buf);
1708 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1709 b = g_new0(struct sipe_buddy, 1);
1710 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1711 b->name = g_strdup(buddy->name);
1712 g_hash_table_insert(sip->buddies, b->name, b);
1713 sipe_group_buddy(gc, b->name, NULL, group->name);
1714 sipe_subscribe_to_name_single(sip, b->name); //@TODO should go to callback
1715 } else {
1716 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1720 static void sipe_free_buddy(struct sipe_buddy *buddy)
1722 g_free(buddy->name);
1723 g_free(buddy->annotation);
1724 g_free(buddy->device_name);
1725 g_slist_free(buddy->groups);
1726 g_free(buddy);
1730 * Unassociates buddy from group first.
1731 * Then see if no groups left, removes buddy completely.
1732 * Otherwise updates buddy groups on server.
1734 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1736 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1737 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1738 struct sipe_group *g = NULL;
1740 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1742 if (!b) return;
1744 if (group) {
1745 g = sipe_group_find_by_name(sip, group->name);
1748 if (g) {
1749 b->groups = g_slist_remove(b->groups, g);
1750 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1753 if (g_slist_length(b->groups) < 1) {
1754 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1755 sipe_cancel_scheduled_action(sip, action_name);
1756 g_free(action_name);
1758 g_hash_table_remove(sip->buddies, buddy->name);
1760 if (b->name) {
1761 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1762 send_soap_request(sip, body);
1763 g_free(body);
1766 sipe_free_buddy(b);
1767 } else {
1768 //updates groups on server
1769 sipe_group_set_user(sip, b->name);
1774 static void
1775 sipe_rename_group(PurpleConnection *gc,
1776 const char *old_name,
1777 PurpleGroup *group,
1778 GList *moved_buddies)
1780 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1781 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1782 if (group) {
1783 sipe_group_rename(sip, s_group, group->name);
1784 } else {
1785 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1789 static void
1790 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1792 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1793 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1794 if (s_group) {
1795 gchar *body;
1796 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1797 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1798 send_soap_request(sip, body);
1799 g_free(body);
1801 sip->groups = g_slist_remove(sip->groups, s_group);
1802 g_free(s_group->name);
1803 } else {
1804 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1808 static GList *sipe_status_types(PurpleAccount *acc)
1810 PurpleStatusType *type;
1811 GList *types = NULL;
1813 // Online
1814 type = purple_status_type_new_with_attrs(
1815 PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE,
1816 // Translators: noun
1817 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1818 NULL);
1819 types = g_list_append(types, type);
1821 // Busy
1822 type = purple_status_type_new_with_attrs(
1823 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1824 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1825 NULL);
1826 types = g_list_append(types, type);
1828 // Do Not Disturb (Not let user set it)
1829 type = purple_status_type_new_with_attrs(
1830 PURPLE_STATUS_UNAVAILABLE, "do-not-disturb", "Do Not Disturb", TRUE, FALSE, FALSE,
1831 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1832 NULL);
1833 types = g_list_append(types, type);
1835 // Be Right Back
1836 type = purple_status_type_new_with_attrs(
1837 PURPLE_STATUS_AWAY, "be-right-back", _("Be Right Back"), TRUE, TRUE, FALSE,
1838 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1839 NULL);
1840 types = g_list_append(types, type);
1842 // Away
1843 type = purple_status_type_new_with_attrs(
1844 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1845 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1846 NULL);
1847 types = g_list_append(types, type);
1849 //On The Phone
1850 type = purple_status_type_new_with_attrs(
1851 PURPLE_STATUS_UNAVAILABLE, "on-the-phone", _("On The Phone"), TRUE, TRUE, FALSE,
1852 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1853 NULL);
1854 types = g_list_append(types, type);
1856 //Out To Lunch
1857 type = purple_status_type_new_with_attrs(
1858 PURPLE_STATUS_AWAY, "out-to-lunch", "Out To Lunch", TRUE, TRUE, FALSE,
1859 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1860 NULL);
1861 types = g_list_append(types, type);
1863 //Appear Offline
1864 type = purple_status_type_new_full(
1865 PURPLE_STATUS_INVISIBLE, NULL, "Appear Offline", TRUE, TRUE, FALSE);
1866 types = g_list_append(types, type);
1868 // Offline
1869 type = purple_status_type_new_full(
1870 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1871 types = g_list_append(types, type);
1873 return types;
1877 * A callback for g_hash_table_foreach
1879 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1881 sipe_subscribe_to_name_single(sip, buddy->name);
1885 * Removes entries from purple buddy list
1886 * that does not correspond ones in the roaming contact list.
1888 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1889 GSList *buddies = purple_find_buddies(sip->account, NULL);
1890 GSList *entry = buddies;
1891 struct sipe_buddy *buddy;
1892 PurpleBuddy *b;
1893 PurpleGroup *g;
1895 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1896 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1897 while (entry) {
1898 b = entry->data;
1899 g = purple_buddy_get_group(b);
1900 buddy = g_hash_table_lookup(sip->buddies, b->name);
1901 if(buddy) {
1902 gboolean in_sipe_groups = FALSE;
1903 GSList *entry2 = buddy->groups;
1904 while (entry2) {
1905 struct sipe_group *group = entry2->data;
1906 if (!strcmp(group->name, g->name)) {
1907 in_sipe_groups = TRUE;
1908 break;
1910 entry2 = entry2->next;
1912 if(!in_sipe_groups) {
1913 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1914 purple_blist_remove_buddy(b);
1916 } else {
1917 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1918 purple_blist_remove_buddy(b);
1920 entry = entry->next;
1924 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1926 int len = msg->bodylen;
1928 gchar *tmp = sipmsg_find_header(msg, "Event");
1929 xmlnode *item;
1930 xmlnode *isc;
1931 const gchar *contacts_delta;
1932 xmlnode *group_node;
1933 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1934 return FALSE;
1937 purple_debug_info("sipe", "msg->body:%s\n", msg->body);
1939 /* Convert the contact from XML to Purple Buddies */
1940 isc = xmlnode_from_str(msg->body, len);
1941 if (!isc) {
1942 return FALSE;
1945 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1946 if (contacts_delta) {
1947 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1950 /* Parse groups */
1951 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1952 struct sipe_group * group = g_new0(struct sipe_group, 1);
1953 const char *name = xmlnode_get_attrib(group_node, "name");
1955 if (!strncmp(name, "~", 1)) {
1956 // TODO translate
1957 name = "Other Contacts";
1959 group->name = g_strdup(name);
1960 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1962 sipe_group_add(sip, group);
1965 // Make sure we have at least one group
1966 if (g_slist_length(sip->groups) == 0) {
1967 struct sipe_group * group = g_new0(struct sipe_group, 1);
1968 PurpleGroup *purple_group;
1969 // TODO translate
1970 group->name = g_strdup("Other Contacts");
1971 group->id = 1;
1972 purple_group = purple_group_new(group->name);
1973 purple_blist_add_group(purple_group, NULL);
1974 sip->groups = g_slist_append(sip->groups, group);
1977 /* Parse contacts */
1978 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1979 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1980 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1981 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1982 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1983 gchar **item_groups;
1984 struct sipe_group *group = NULL;
1985 struct sipe_buddy *buddy = NULL;
1986 int i = 0;
1988 // assign to group Other Contacts if nothing else received
1989 if(!groups || !strcmp("", groups) ) {
1990 group = sipe_group_find_by_name(sip, "Other Contacts");
1991 groups = group ? g_strdup_printf("%d", group->id) : "1";
1994 item_groups = g_strsplit(groups, " ", 0);
1996 while (item_groups[i]) {
1997 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1999 // If couldn't find the right group for this contact, just put them in the first group we have
2000 if (group == NULL && g_slist_length(sip->groups) > 0) {
2001 group = sip->groups->data;
2004 if (group != NULL) {
2005 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2006 if (!b){
2007 b = purple_buddy_new(sip->account, buddy_name, uri);
2008 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2011 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2012 if (name != NULL && strlen(name) != 0) {
2013 purple_blist_alias_buddy(b, name);
2017 if (!buddy) {
2018 buddy = g_new0(struct sipe_buddy, 1);
2019 buddy->name = g_strdup(b->name);
2020 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2023 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2025 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2026 } else {
2027 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2028 name);
2031 i++;
2032 } // while, contact groups
2033 g_strfreev(item_groups);
2034 g_free(groups);
2035 g_free(name);
2036 g_free(buddy_name);
2037 g_free(uri);
2039 } // for, contacts
2041 xmlnode_free(isc);
2043 sipe_cleanup_local_blist(sip);
2045 //subscribe to buddies
2046 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2047 //if(sip->msrtc_event_categories){
2048 sipe_subscribe_to_buddies_batched(sip);
2049 //}else{
2050 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2052 sip->subscribed_buddies = TRUE;
2055 return 0;
2061 * Subscribe roaming contacts
2063 static void sipe_subscribe_buddylist(struct sipe_account_data *sip,struct sipmsg *msg)
2065 gchar *to = g_strdup_printf("sip:%s", sip->username);
2066 gchar *tmp = get_contact(sip);
2067 gchar *hdr = g_strdup_printf(
2068 "Event: vnd-microsoft-roaming-contacts\r\n"
2069 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2070 "Supported: com.microsoft.autoextend\r\n"
2071 "Supported: ms-benotify\r\n"
2072 "Proxy-Require: ms-benotify\r\n"
2073 "Supported: ms-piggyback-first-notify\r\n"
2074 "Contact: %s\r\n", tmp);
2075 g_free(tmp);
2077 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_roaming_contacts);
2078 g_free(to);
2079 g_free(hdr);
2082 static gboolean
2083 sipe_process_pending_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2085 sipe_process_incoming_pending (sip, msg);
2086 return TRUE;
2089 static void sipe_subscribe_pending_buddies(struct sipe_account_data *sip,struct sipmsg *msg)
2091 gchar *to = g_strdup_printf("sip:%s", sip->username);
2092 gchar *tmp = get_contact(sip);
2093 gchar *hdr = g_strdup_printf(
2094 "Event: presence.wpending\r\n"
2095 "Accept: text/xml+msrtc.wpending\r\n"
2096 "Supported: com.microsoft.autoextend\r\n"
2097 "Supported: ms-benotify\r\n"
2098 "Proxy-Require: ms-benotify\r\n"
2099 "Supported: ms-piggyback-first-notify\r\n"
2100 "Contact: %s\r\n", tmp);
2101 g_free(tmp);
2103 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_pending_response);
2104 g_free(to);
2105 g_free(hdr);
2108 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2110 const gchar *contacts_delta;
2111 xmlnode *xml;
2113 xml = xmlnode_from_str(msg->body, msg->bodylen);
2114 if (!xml)
2116 return;
2119 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2120 if (contacts_delta)
2122 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2125 xmlnode_free(xml);
2128 static gboolean
2129 sipe_process_acl_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2131 sipe_process_roaming_acl(sip, msg);
2132 return TRUE;
2136 * When we receive some self (BE) NOTIFY with a new subscriber
2137 * we sends a setSubscribers request to him [SIP-PRES]
2141 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2143 gchar *contact;
2144 gchar *to;
2145 xmlnode *xml;
2146 xmlnode *node;
2148 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2150 xml = xmlnode_from_str(msg->body, msg->bodylen);
2151 if (!xml) return;
2153 contact = get_contact(sip);
2154 to = g_strdup_printf("sip:%s", sip->username);
2156 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2157 const char *user;
2158 gchar *hdr;
2159 gchar *body;
2161 user = xmlnode_get_attrib(node, "user");
2162 if (!user) continue;
2164 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2166 hdr = g_strdup_printf(
2167 "Contact: %s\r\n"
2168 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2170 body = g_strdup_printf(
2171 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2172 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2173 "</setSubscribers>", user);
2175 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2176 g_free(body);
2177 g_free(hdr);
2180 g_free(to);
2181 g_free(contact);
2182 xmlnode_free(xml);
2185 static void sipe_subscribe_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2187 gchar *to = g_strdup_printf("sip:%s", sip->username);
2188 gchar *tmp = get_contact(sip);
2189 gchar *hdr = g_strdup_printf(
2190 "Event: vnd-microsoft-roaming-ACL\r\n"
2191 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2192 "Supported: com.microsoft.autoextend\r\n"
2193 "Supported: ms-benotify\r\n"
2194 "Proxy-Require: ms-benotify\r\n"
2195 "Supported: ms-piggyback-first-notify\r\n"
2196 "Contact: %s\r\n", tmp);
2197 g_free(tmp);
2199 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_acl_response);
2200 g_free(to);
2201 g_free(hdr);
2205 * To request for presence information about the user, access level settings that have already been configured by the user
2206 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2207 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2210 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2212 gchar *to = g_strdup_printf("sip:%s", sip->username);
2213 gchar *tmp = get_contact(sip);
2214 gchar *hdr = g_strdup_printf(
2215 "Event: vnd-microsoft-roaming-self\r\n"
2216 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2217 "Supported: com.microsoft.autoextend\r\n"
2218 "Supported: ms-benotify\r\n"
2219 "Proxy-Require: ms-benotify\r\n"
2220 "Supported: ms-piggyback-first-notify\r\n"
2221 "Contact: %s\r\n"
2222 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2224 gchar *body=g_strdup(
2225 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2226 "<roaming type=\"categories\"/>"
2227 "<roaming type=\"containers\"/>"
2228 "<roaming type=\"subscribers\"/></roamingList>");
2230 g_free(tmp);
2231 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
2232 g_free(body);
2233 g_free(to);
2234 g_free(hdr);
2237 /** Subscription for provisioning information to help with initial
2238 * configuration. This subscription is a one-time query (denoted by the Expires header,
2239 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2240 * configuration, meeting policies, and policy settings that Communicator must enforce.
2241 * TODO: for what we need this information.
2244 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2246 gchar *to = g_strdup_printf("sip:%s", sip->username);
2247 gchar *tmp = get_contact(sip);
2248 gchar *hdr = g_strdup_printf(
2249 "Event: vnd-microsoft-provisioning-v2\r\n"
2250 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2251 "Supported: com.microsoft.autoextend\r\n"
2252 "Supported: ms-benotify\r\n"
2253 "Proxy-Require: ms-benotify\r\n"
2254 "Supported: ms-piggyback-first-notify\r\n"
2255 "Expires: 0\r\n"
2256 "Contact: %s\r\n"
2257 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2258 gchar *body = g_strdup(
2259 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2260 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2261 "<provisioningGroup name=\"ucPolicy\"/>"
2262 "</provisioningGroupList>");
2264 g_free(tmp);
2265 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
2266 g_free(body);
2267 g_free(to);
2268 g_free(hdr);
2271 /* IM Session (INVITE and MESSAGE methods) */
2273 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2275 struct sip_im_session *session;
2276 GSList *entry;
2277 if (sip == NULL || who == NULL) {
2278 return NULL;
2281 entry = sip->im_sessions;
2282 while (entry) {
2283 session = entry->data;
2284 if ((who != NULL && !strcmp(who, session->with))) {
2285 return session;
2287 entry = entry->next;
2289 return NULL;
2292 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2294 struct sip_im_session *session = find_im_session(sip, who);
2295 if (!session) {
2296 session = g_new0(struct sip_im_session, 1);
2297 session->with = g_strdup(who);
2298 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2300 return session;
2303 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2305 struct sip_dialog *dialog = session->dialog;
2306 GSList *entry;
2308 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2310 if (dialog) {
2311 entry = dialog->routes;
2312 while (entry) {
2313 g_free(entry->data);
2314 entry = g_slist_remove(entry, entry->data);
2316 entry = dialog->supported;
2317 while (entry) {
2318 g_free(entry->data);
2319 entry = g_slist_remove(entry, entry->data);
2321 g_free(dialog->callid);
2322 g_free(dialog->ourtag);
2323 g_free(dialog->theirtag);
2324 g_free(dialog->theirepid);
2325 g_free(dialog->request);
2327 g_free(session->dialog);
2329 entry = session->outgoing_message_queue;
2330 while (entry) {
2331 g_free(entry->data);
2332 entry = g_slist_remove(entry, entry->data);
2335 g_free(session->with);
2336 g_free(session);
2339 static gboolean
2340 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2342 gboolean ret = TRUE;
2344 if (msg->response != 200) {
2345 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2346 return FALSE;
2349 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2351 return ret;
2355 * Asks UA/proxy about its capabilities.
2357 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2359 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2360 gchar *contact = get_contact(sip);
2361 gchar *request;
2362 request = g_strdup_printf(
2363 "Accept: application/sdp\r\n"
2364 "Contact: %s\r\n", contact);
2366 g_free(contact);
2368 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2370 g_free(to);
2371 g_free(request);
2374 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2376 char *msg, *msg_tmp;
2377 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2378 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2379 g_free(msg_tmp);
2380 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2381 "possibly because one or more persons are offline:\n%s") ,
2382 msg ? msg : "");
2383 purple_conv_present_error(with, sip->account, msg_tmp);
2384 g_free(msg);
2385 g_free(msg_tmp);
2388 static void sipe_im_remove_first_from_queue (struct sip_im_session * session);
2389 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2391 static gboolean
2392 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2394 gboolean ret = TRUE;
2395 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2396 struct sip_im_session * session = find_im_session(sip, with);
2397 struct sip_dialog *dialog;
2399 if (!session) {
2400 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2401 g_free(with);
2402 return FALSE;
2405 if (msg->response != 200) {
2406 gchar *queued_msg = NULL;
2407 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2409 if (session->outgoing_message_queue) {
2410 queued_msg = session->outgoing_message_queue->data;
2412 sipe_present_message_undelivered_err(with, sip, queued_msg);
2413 im_session_destroy(sip, session);
2414 g_free(with);
2415 return FALSE;
2418 dialog = session->dialog;
2419 if (!dialog) {
2420 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2421 ret = FALSE;
2424 sipe_im_remove_first_from_queue(session);
2425 sipe_im_process_queue(sip, session);
2426 g_free(with);
2427 return ret;
2430 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2432 gchar *hdr;
2433 gchar *fullto;
2434 gchar *tmp;
2435 char *msgformat;
2436 char *msgtext;
2437 gchar *msgr_value;
2438 gchar *msgr;
2440 if (strncmp("sip:", session->with, 4)) {
2441 fullto = g_strdup_printf("sip:%s", session->with);
2442 } else {
2443 fullto = g_strdup(session->with);
2446 sipe_parse_html(msg, &msgformat, &msgtext);
2447 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2449 msgr_value = sipmsg_get_msgr_string(msgformat);
2450 g_free(msgformat);
2451 if (msgr_value) {
2452 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2453 g_free(msgr_value);
2454 } else {
2455 msgr = g_strdup("");
2458 tmp = get_contact(sip);
2459 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2460 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2461 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2462 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2463 tmp, msgr);
2464 g_free(tmp);
2465 g_free(msgr);
2467 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2468 g_free(msgtext);
2469 g_free(hdr);
2470 g_free(fullto);
2474 static void
2475 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2477 GSList *entry = session->outgoing_message_queue;
2478 if (entry) {
2479 char *queued_msg = entry->data;
2480 sipe_send_message(sip, session, queued_msg);
2484 static void
2485 sipe_im_remove_first_from_queue (struct sip_im_session * session)
2487 if (session && session->outgoing_message_queue) {
2488 char *queued_msg = session->outgoing_message_queue->data;
2489 // Remove from the queue and free the string
2490 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2491 g_free(queued_msg);
2495 static void
2496 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2498 GSList *hdr = msg->headers;
2499 struct siphdrelement *elem;
2500 gchar *contact;
2502 while(hdr)
2504 elem = hdr->data;
2505 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2507 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2508 dialog->routes = g_slist_append(dialog->routes, route);
2510 hdr = g_slist_next(hdr);
2513 if (outgoing)
2515 dialog->routes = g_slist_reverse(dialog->routes);
2518 if (dialog->routes)
2520 dialog->request = dialog->routes->data;
2521 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2524 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2525 dialog->routes = g_slist_append(dialog->routes, contact);
2528 static void
2529 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2531 GSList *hdr = msg->headers;
2532 struct siphdrelement *elem;
2533 while(hdr)
2535 elem = hdr->data;
2536 if(!g_ascii_strcasecmp(elem->name, "Supported")
2537 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2539 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2542 hdr = g_slist_next(hdr);
2546 static void
2547 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2549 gchar *us = outgoing ? "From" : "To";
2550 gchar *them = outgoing ? "To" : "From";
2552 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2553 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2554 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2555 if (!dialog->theirepid) {
2556 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2558 if (!dialog->theirepid) {
2559 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2562 sipe_get_route_header(msg, dialog, outgoing);
2563 sipe_get_supported_header(msg, dialog, outgoing);
2567 static gboolean
2568 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2570 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2571 struct sip_im_session * session = find_im_session(sip, with);
2572 struct sip_dialog *dialog;
2574 if (!session) {
2575 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2576 g_free(with);
2577 return FALSE;
2580 if (msg->response != 200) {
2581 gchar *queued_msg = NULL;
2582 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2584 if (session->outgoing_message_queue) {
2585 queued_msg = session->outgoing_message_queue->data;
2587 sipe_present_message_undelivered_err(with, sip, queued_msg);
2589 im_session_destroy(sip, session);
2590 g_free(with);
2591 return FALSE;
2594 dialog = session->dialog;
2595 if (!dialog) {
2596 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2597 g_free(with);
2598 return FALSE;
2601 sipe_parse_dialog(msg, dialog, TRUE);
2602 dialog->cseq = 0;
2604 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2605 session->outgoing_invite = NULL;
2606 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2607 sipe_im_remove_first_from_queue(session);
2608 } else {
2609 sipe_im_process_queue(sip, session);
2612 g_free(with);
2613 return TRUE;
2617 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
2619 gchar *hdr;
2620 gchar *to;
2621 gchar *contact;
2622 gchar *body;
2623 char *msgformat;
2624 char *msgtext;
2625 char *base64_msg;
2626 char *ms_text_format;
2627 gchar *msgr_value;
2628 gchar *msgr;
2630 if (session->dialog) {
2631 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2632 return;
2635 session->dialog = g_new0(struct sip_dialog, 1);
2637 if (strstr(session->with, "sip:")) {
2638 to = g_strdup(session->with);
2639 } else {
2640 to = g_strdup_printf("sip:%s", session->with);
2643 sipe_parse_html(msg_body, &msgformat, &msgtext);
2644 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2646 msgr_value = sipmsg_get_msgr_string(msgformat);
2647 g_free(msgformat);
2648 msgr = "";
2649 if (msgr_value) {
2650 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2651 g_free(msgr_value);
2654 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2655 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2656 g_free(msgtext);
2657 g_free(msgr);
2658 g_free(base64_msg);
2660 contact = get_contact(sip);
2661 hdr = g_strdup_printf(
2662 "Contact: %s\r\n%s"
2663 "Content-Type: application/sdp\r\n",
2664 contact, ms_text_format);
2665 g_free(ms_text_format);
2667 body = g_strdup_printf(
2668 "v=0\r\n"
2669 "o=- 0 0 IN IP4 %s\r\n"
2670 "s=session\r\n"
2671 "c=IN IP4 %s\r\n"
2672 "t=0 0\r\n"
2673 "m=message %d sip null\r\n"
2674 "a=accept-types:text/plain text/html image/gif "
2675 "multipart/alternative application/im-iscomposing+xml\r\n",
2676 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2678 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2679 to, to, hdr, body, session->dialog, process_invite_response);
2681 g_free(to);
2682 g_free(body);
2683 g_free(hdr);
2684 g_free(contact);
2687 static void
2688 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2690 if (session) {
2691 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2692 im_session_destroy(sip, session);
2696 static void
2697 sipe_convo_closed(PurpleConnection * gc, const char *who)
2699 struct sipe_account_data *sip = gc->proto_data;
2701 purple_debug_info("sipe", "conversation with %s closed\n", who);
2702 im_session_close(sip, find_im_session(sip, who));
2705 static void
2706 im_session_close_all (struct sipe_account_data *sip)
2708 GSList *entry = sip->im_sessions;
2709 while (entry) {
2710 im_session_close (sip, entry->data);
2711 entry = sip->im_sessions;
2715 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2717 struct sipe_account_data *sip;
2718 char *to;
2719 char *text;
2720 struct sip_im_session *session;
2722 purple_debug_info("sipe", "sipe_im_send what=%s\n", what);
2724 sip = gc->proto_data;
2725 to = g_strdup(who);
2726 text = g_strdup(what);
2728 session = find_or_create_im_session(sip, who);
2730 // Queue the message
2731 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
2733 if (session->dialog && session->dialog->callid) {
2734 sipe_im_process_queue(sip, session);
2735 } else if (!session->outgoing_invite) {
2736 // Need to send the INVITE to get the outgoing dialog setup
2737 sipe_invite(sip, session, text);
2740 g_free(to);
2741 return 1;
2745 /* End IM Session (INVITE and MESSAGE methods) */
2747 static unsigned int
2748 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2750 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2751 struct sip_im_session *session;
2753 if (state == PURPLE_NOT_TYPING)
2754 return 0;
2756 session = find_im_session(sip, who);
2758 if (session && session->dialog) {
2759 send_sip_request(gc, "INFO", who, who,
2760 "Content-Type: application/xml\r\n",
2761 SIPE_SEND_TYPING, session->dialog, NULL);
2764 return SIPE_TYPING_SEND_TIMEOUT;
2767 static gboolean resend_timeout(struct sipe_account_data *sip)
2769 GSList *tmp = sip->transactions;
2770 time_t currtime = time(NULL);
2771 while (tmp) {
2772 struct transaction *trans = tmp->data;
2773 tmp = tmp->next;
2774 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2775 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2776 /* TODO 408 */
2777 } else {
2778 if ((currtime - trans->time > 2) && trans->retries == 0) {
2779 trans->retries++;
2780 sendout_sipmsg(sip, trans->msg);
2784 return TRUE;
2787 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2789 /* register again when security token expires */
2790 /* we have to start a new authentication as the security token
2791 * is almost expired by sending a not signed REGISTER message */
2792 purple_debug_info("sipe", "do a full reauthentication\n");
2793 sipe_auth_free(&sip->registrar);
2794 sip->registerstatus = 0;
2795 do_register(sip);
2796 sip->reauthenticate_set = FALSE;
2799 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2801 gchar *from;
2802 gchar *contenttype;
2803 gboolean found = FALSE;
2805 from = parse_from(sipmsg_find_header(msg, "From"));
2807 if (!from) return;
2809 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2811 contenttype = sipmsg_find_header(msg, "Content-Type");
2812 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2813 gchar *msgr = sipmsg_find_part_of_header(contenttype, "msgr=", NULL, NULL);
2814 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2816 gchar *body_esc = g_markup_escape_text(msg->body, -1);
2817 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2818 g_free(msgr);
2819 g_free(body_esc);
2820 g_free(x_mms_im_format);
2822 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2823 g_free(body_html);
2824 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2825 found = TRUE;
2826 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2827 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2828 xmlnode *state;
2829 gchar *statedata;
2831 if (!isc) {
2832 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2833 return;
2836 state = xmlnode_get_child(isc, "state");
2838 if (!state) {
2839 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2840 xmlnode_free(isc);
2841 return;
2844 statedata = xmlnode_get_data(state);
2845 if (statedata) {
2846 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2847 else serv_got_typing_stopped(sip->gc, from);
2849 g_free(statedata);
2851 xmlnode_free(isc);
2852 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2853 found = TRUE;
2855 if (!found) {
2856 purple_debug_info("sipe", "got unknown mime-type");
2857 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2859 g_free(from);
2862 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2864 gchar *ms_text_format;
2865 gchar *from;
2866 gchar *body;
2867 struct sip_im_session *session;
2869 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
2871 // Only accept text invitations
2872 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2873 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2874 return;
2877 from = parse_from(sipmsg_find_header(msg, "From"));
2878 session = find_or_create_im_session (sip, from);
2879 if (session) {
2880 if (session->dialog) {
2881 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2882 } else {
2883 session->dialog = g_new0(struct sip_dialog, 1);
2885 sipe_parse_dialog(msg, session->dialog, FALSE);
2887 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2888 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2889 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2890 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2892 } else {
2893 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2896 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2897 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2898 if (ms_text_format && !strncmp(ms_text_format, "text/plain", 10)) {
2899 gchar *msgr = sipmsg_find_part_of_header(ms_text_format, "msgr=", ";", NULL);
2900 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2902 gchar *ms_body = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
2903 g_free(msgr);
2904 if (ms_body) {
2905 gchar *body = purple_base64_decode(ms_body, NULL);
2906 gchar *body_esc = g_markup_escape_text(body, -1);
2907 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2908 g_free(ms_body);
2909 g_free(body_esc);
2910 g_free(body);
2911 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2912 g_free(body_html);
2913 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2915 g_free(x_mms_im_format);
2917 g_free(from);
2919 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2920 sipmsg_remove_header(msg, "Ms-Text-Format");
2921 sipmsg_remove_header(msg, "EndPoints");
2922 sipmsg_remove_header(msg, "User-Agent");
2923 sipmsg_remove_header(msg, "Roster-Manager");
2925 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2926 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2928 body = g_strdup_printf(
2929 "v=0\r\n"
2930 "o=- 0 0 IN IP4 %s\r\n"
2931 "s=session\r\n"
2932 "c=IN IP4 %s\r\n"
2933 "t=0 0\r\n"
2934 "m=message %d sip sip:%s\r\n"
2935 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2936 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2937 sip->realport, sip->username);
2938 send_sip_response(sip->gc, msg, 200, "OK", body);
2939 g_free(body);
2942 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
2944 gchar *from;
2945 gchar *body;
2946 struct sip_im_session *session;
2949 from = parse_from(sipmsg_find_header(msg, "From"));
2950 session = find_or_create_im_session (sip, from);
2951 if (session) {
2952 if (session->dialog) {
2953 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2954 } else {
2955 session->dialog = g_new0(struct sip_dialog, 1);
2957 sipe_parse_dialog(msg, session->dialog, FALSE);
2959 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2960 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2961 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2962 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2964 } else {
2965 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2968 g_free(from);
2970 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2971 sipmsg_remove_header(msg, "EndPoints");
2972 sipmsg_remove_header(msg, "User-Agent");
2974 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
2975 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2977 body = g_strdup_printf(
2978 "v=0\r\n"
2979 "o=- 0 0 IN IP4 0.0.0.0\r\n"
2980 "s=session\r\n"
2981 "c=IN IP4 0.0.0.0\r\n"
2982 "t=0 0\r\n"
2983 "m=message %d sip sip:%s\r\n"
2984 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2985 sip->realport, sip->username);
2986 send_sip_response(sip->gc, msg, 200, "OK", body);
2987 g_free(body);
2990 static void sipe_connection_cleanup(struct sipe_account_data *);
2991 static void create_connection(struct sipe_account_data *, gchar *, int);
2993 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2995 gchar *tmp;
2996 //gchar krb5_token;
2997 const gchar *expires_header;
2998 int expires;
2999 GSList *hdr = msg->headers;
3000 struct siphdrelement *elem;
3002 expires_header = sipmsg_find_header(msg, "Expires");
3003 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3004 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3006 switch (msg->response) {
3007 case 200:
3008 if (expires == 0) {
3009 sip->registerstatus = 0;
3010 } else {
3011 int i = 0;
3012 gchar *contact_hdr = NULL;
3013 gchar *gruu = NULL;
3014 gchar *epid;
3015 gchar *uuid;
3017 sip->registerexpire = expires;
3019 if (!sip->reregister_set) {
3020 gchar *action_name = g_strdup_printf("<%s>", "registration");
3021 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
3022 g_free(action_name);
3023 sip->reregister_set = TRUE;
3026 sip->registerstatus = 3;
3028 if (!sip->reauthenticate_set) {
3029 /* we have to reauthenticate as our security token expires
3030 after eight hours (be five minutes early) */
3031 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3032 guint reauth_timeout = (8 * 3600) - 360;
3033 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3034 g_free(action_name);
3035 sip->reauthenticate_set = TRUE;
3038 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3040 epid = get_epid();
3041 uuid = generateUUIDfromEPID(epid);
3042 g_free(epid);
3044 // There can be multiple Contact headers (one per location where the user is logged in) so
3045 // make sure to only get the one for this uuid
3046 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3047 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3048 if (valid_contact) {
3049 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3050 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3051 g_free(valid_contact);
3052 break;
3053 } else {
3054 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3057 g_free(uuid);
3059 g_free(sip->contact);
3060 if(gruu) {
3061 sip->contact = g_strdup_printf("<%s>", gruu);
3062 g_free(gruu);
3063 } else {
3064 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3065 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);
3067 sip->msrtc_event_categories = FALSE;
3069 while(hdr)
3071 elem = hdr->data;
3072 if(!g_ascii_strcasecmp(elem->name, "Supported"))
3074 if (strstr(elem->value, "msrtc-event-categories")){
3075 sip->msrtc_event_categories = TRUE;
3077 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s, %d\n", elem->value, sip->msrtc_event_categories);
3079 hdr = g_slist_next(hdr);
3082 if (!sip->subscribed) { //do it just once, not every re-register
3083 tmp = sipmsg_find_header(msg, "Allow-Events");
3084 sipe_options_request(sip, sip->sipdomain);
3085 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
3086 sipe_subscribe_buddylist(sip, msg);
3088 sipe_subscribe_acl(sip, msg);
3089 sipe_subscribe_roaming_self(sip, msg);
3090 sipe_subscribe_roaming_provisioning(sip, msg);
3091 sipe_subscribe_pending_buddies(sip, msg);
3092 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3093 sip->subscribed = TRUE;
3096 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
3097 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
3098 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
3099 } else {
3100 tmp = sipmsg_find_header(msg, "ms-keep-alive");
3101 if (tmp) {
3102 sipe_keep_alive_timeout(sip, tmp);
3106 // Should we remove the transaction here?
3107 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3108 transactions_remove(sip, tc);
3110 break;
3111 case 301:
3113 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3115 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3116 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3117 gchar **tmp;
3118 gchar *hostname;
3119 int port = 0;
3120 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3121 int i = 1;
3123 tmp = g_strsplit(parts[0], ":", 0);
3124 hostname = g_strdup(tmp[0]);
3125 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3126 g_strfreev(tmp);
3128 while (parts[i]) {
3129 tmp = g_strsplit(parts[i], "=", 0);
3130 if (tmp[1]) {
3131 if (g_strcasecmp("transport", tmp[0]) == 0) {
3132 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3133 transport = SIPE_TRANSPORT_TCP;
3134 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3135 transport = SIPE_TRANSPORT_UDP;
3139 g_strfreev(tmp);
3140 i++;
3142 g_strfreev(parts);
3144 /* Close old connection */
3145 sipe_connection_cleanup(sip);
3147 /* Create new connection */
3148 sip->transport = transport;
3149 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3150 hostname, port, TRANSPORT_DESCRIPTOR);
3151 create_connection(sip, hostname, port);
3153 g_free(redirect);
3155 break;
3156 case 401:
3157 if (sip->registerstatus != 2) {
3158 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3159 if (sip->registrar.retries > 3) {
3160 sip->gc->wants_to_die = TRUE;
3161 purple_connection_error(sip->gc, _("Wrong Password"));
3162 return TRUE;
3164 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3165 tmp = sipmsg_find_auth_header(msg, "NTLM");
3166 } else {
3167 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3169 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3170 fill_auth(sip, tmp, &sip->registrar);
3171 sip->registerstatus = 2;
3172 if (sip->account->disconnecting) {
3173 do_register_exp(sip, 0);
3174 } else {
3175 do_register(sip);
3178 break;
3179 case 403:
3181 const gchar *warning = sipmsg_find_header(msg, "Warning");
3182 if (warning != NULL) {
3183 /* Example header:
3184 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3186 gchar **tmp = g_strsplit(warning, "\"", 0);
3187 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3188 g_strfreev(tmp);
3189 } else {
3190 warning = _("You have been rejected by the server");
3193 sip->gc->wants_to_die = TRUE;
3194 purple_connection_error(sip->gc, warning);
3195 return TRUE;
3197 break;
3198 case 404:
3200 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3201 if (warning != NULL) {
3202 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3203 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3204 g_free(reason);
3205 } else {
3206 warning = _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3209 sip->gc->wants_to_die = TRUE;
3210 purple_connection_error(sip->gc, warning);
3211 return TRUE;
3213 break;
3214 case 503:
3216 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3217 if (warning != NULL) {
3218 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3219 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3220 g_free(reason);
3221 } else {
3222 warning = _("Service unavailable: no reason given");
3225 sip->gc->wants_to_die = TRUE;
3226 purple_connection_error(sip->gc, warning);
3227 return TRUE;
3229 break;
3231 return TRUE;
3234 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3236 const char *uri;
3237 xmlnode *xn_categories;
3238 xmlnode *xn_category;
3239 xmlnode *xn_node;
3240 int changed = 0;
3241 const char *activity = NULL;
3243 xn_categories = xmlnode_from_str(data, len);
3244 uri = xmlnode_get_attrib(xn_categories, "uri");
3246 for (xn_category = xmlnode_get_child(xn_categories, "category");
3247 xn_category ;
3248 xn_category = xmlnode_get_next_twin(xn_category) )
3250 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3252 if (!strcmp(attrVar, "note"))
3254 char *note;
3255 struct sipe_buddy *sbuddy;
3256 xn_node = xmlnode_get_child(xn_category, "note");
3257 if (!xn_node) continue;
3258 xn_node = xmlnode_get_child(xn_node, "body");
3259 if (!xn_node) continue;
3261 note = xmlnode_get_data(xn_node);
3263 if(uri){
3264 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3266 if (sbuddy && note)
3268 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3269 sbuddy->annotation = g_strdup(note);
3270 changed = 1;
3273 g_free(note);
3275 else if(!strcmp(attrVar, "state"))
3277 char *data;
3278 int avail;
3279 xn_node = xmlnode_get_child(xn_category, "state");
3280 if (!xn_node) continue;
3281 xn_node = xmlnode_get_child(xn_node, "availability");
3282 if (!xn_node) continue;
3284 data = xmlnode_get_data(xn_node);
3285 avail = atoi(data);
3286 g_free(data);
3288 if (avail < 3000)
3289 activity = "unknown";
3290 else if (avail < 4500)
3291 activity = "available";
3292 else if (avail < 6000)
3293 activity = "idle";
3294 else if (avail < 7500)
3295 activity = "busy";
3296 else if (avail < 9000)
3297 activity = "busy";
3298 else if (avail < 12000)
3299 activity = "dnd";
3300 else if (avail < 18000)
3301 activity = "away";
3302 else
3303 activity = "offline";
3305 changed = 1;
3308 if (changed)
3310 if(activity){
3311 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n",activity);
3312 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3316 xmlnode_free(xn_categories);
3319 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3321 const char *uri,*state;
3322 xmlnode *xn_list;
3323 xmlnode *xn_resource;
3324 xmlnode *xn_instance;
3326 xn_list = xmlnode_from_str(data, len);
3328 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3329 xn_resource;
3330 xn_resource = xmlnode_get_next_twin(xn_resource) )
3332 xn_instance = xmlnode_get_child(xn_resource, "instance");
3333 if (!xn_instance) return;
3335 state = xmlnode_get_attrib(xn_instance, "state");
3336 uri = xmlnode_get_attrib(xn_instance, "cid");
3337 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3338 if(strstr(state,"resubscribe")){
3339 sipe_subscribe_to_name_single(sip, uri);
3344 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3346 const gchar *uri;
3347 gchar *getbasic;
3348 gchar *activity = NULL;
3349 xmlnode *pidf;
3350 xmlnode *basicstatus = NULL, *tuple, *status;
3351 gboolean isonline = FALSE;
3352 xmlnode *display_name_node;
3354 pidf = xmlnode_from_str(data, len);
3355 if (!pidf) {
3356 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3357 return;
3360 uri = xmlnode_get_attrib(pidf, "entity");
3362 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3364 if ((status = xmlnode_get_child(tuple, "status"))) {
3365 basicstatus = xmlnode_get_child(status, "basic");
3369 if (!basicstatus) {
3370 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3371 xmlnode_free(pidf);
3372 return;
3375 getbasic = xmlnode_get_data(basicstatus);
3376 if (!getbasic) {
3377 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3378 xmlnode_free(pidf);
3379 return;
3382 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3383 if (strstr(getbasic, "open")) {
3384 isonline = TRUE;
3386 g_free(getbasic);
3388 display_name_node = xmlnode_get_child(pidf, "display-name");
3389 // updating display name if alias was just URI
3390 if (display_name_node) {
3391 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3392 GSList *entry = buddies;
3393 PurpleBuddy *p_buddy;
3394 char * display_name = xmlnode_get_data(display_name_node);
3396 while (entry) {
3397 const char *server_alias;
3398 char *alias;
3400 p_buddy = entry->data;
3402 alias = (char *)purple_buddy_get_alias(p_buddy);
3403 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3404 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3405 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3406 purple_blist_alias_buddy(p_buddy, display_name);
3408 g_free(alias);
3410 server_alias = purple_buddy_get_server_alias(p_buddy);
3411 if (display_name &&
3412 ( (server_alias && strcmp(display_name, server_alias))
3413 || !server_alias || strlen(server_alias) == 0 )
3415 purple_blist_server_alias_buddy(p_buddy, display_name);
3418 entry = entry->next;
3420 g_free(display_name);
3423 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3424 if ((status = xmlnode_get_child(tuple, "status"))) {
3425 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3426 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3427 activity = xmlnode_get_data(basicstatus);
3428 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3434 if (isonline) {
3435 gchar * status_id = NULL;
3436 if (activity) {
3437 if (strstr(activity, "busy")) {
3438 status_id = "busy";
3439 } else if (strstr(activity, "away")) {
3440 status_id = "away";
3444 if (!status_id) {
3445 status_id = "available";
3448 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3449 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3450 } else {
3451 purple_prpl_got_user_status(sip->account, uri, "offline", NULL);
3454 g_free(activity);
3455 xmlnode_free(pidf);
3458 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3460 const char *availability;
3461 const char *activity;
3462 const char *display_name = NULL;
3463 const char *activity_name;
3464 const char *name;
3465 char *uri;
3466 int avl;
3467 int act;
3468 struct sipe_buddy *sbuddy;
3470 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3472 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3473 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3474 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3475 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3476 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3477 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3478 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3479 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3480 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3481 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3482 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3483 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3485 name = xmlnode_get_attrib(xn_presentity, "uri");
3486 uri = g_strdup_printf("sip:%s", name);
3487 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3488 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3490 // updating display name if alias was just URI
3491 if (xn_display_name) {
3492 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3493 GSList *entry = buddies;
3494 PurpleBuddy *p_buddy;
3495 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3497 while (entry) {
3498 const char *email_str, *server_alias;
3500 p_buddy = entry->data;
3502 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3503 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3504 purple_blist_alias_buddy(p_buddy, display_name);
3507 server_alias = purple_buddy_get_server_alias(p_buddy);
3508 if (display_name &&
3509 ( (server_alias && strcmp(display_name, server_alias))
3510 || !server_alias || strlen(server_alias) == 0 )
3512 purple_blist_server_alias_buddy(p_buddy, display_name);
3515 if (email) {
3516 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3517 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3518 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3522 entry = entry->next;
3526 avl = atoi(availability);
3527 act = atoi(activity);
3529 if (act <= 100)
3530 activity_name = "away";
3531 else if (act <= 150)
3532 activity_name = "out-to-lunch";
3533 else if (act <= 300)
3534 activity_name = "be-right-back";
3535 else if (act <= 400)
3536 activity_name = "available";
3537 else if (act <= 500)
3538 activity_name = "on-the-phone";
3539 else if (act <= 600)
3540 activity_name = "busy";
3541 else
3542 activity_name = "available";
3544 if (avl == 0)
3545 activity_name = "offline";
3547 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3548 if (sbuddy)
3550 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3551 sbuddy->annotation = NULL;
3552 if (note) { sbuddy->annotation = g_strdup(note); }
3554 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3555 sbuddy->device_name = NULL;
3556 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3559 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3560 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3561 g_free(note);
3562 xmlnode_free(xn_presentity);
3563 g_free(uri);
3566 static void process_incoming_notify_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3568 char *ctype = sipmsg_find_header(msg, "Content-Type");
3570 purple_debug_info("sipe", "process_incoming_notify_presence: Content-Type: %s\n", ctype ? ctype : "");
3572 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3573 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3575 const char *content = msg->body;
3576 unsigned length = msg->bodylen;
3577 PurpleMimeDocument *mime = NULL;
3579 if (strstr(ctype, "multipart"))
3581 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3582 const char *content_type;
3583 GList* parts;
3584 mime = purple_mime_document_parse(doc);
3585 parts = purple_mime_document_get_parts(mime);
3586 while(parts) {
3587 content = purple_mime_part_get_data(parts->data);
3588 length = purple_mime_part_get_length(parts->data);
3589 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3590 if(content_type && strstr(content_type,"application/rlmi+xml"))
3592 process_incoming_notify_rlmi_resub(sip, content, length);
3594 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3596 process_incoming_notify_msrtc(sip, content, length);
3598 else
3600 process_incoming_notify_rlmi(sip, content, length);
3602 parts = parts->next;
3604 g_free(doc);
3606 if (mime)
3608 purple_mime_document_free(mime);
3611 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3613 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3615 else if(strstr(ctype, "application/rlmi+xml"))
3617 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3620 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3622 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3624 else
3626 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3631 * Dispatcher for all incoming subscription information
3632 * whether it comes from NOTIFY, BENOTIFY requests or
3633 * piggy-backed to subscription's OK responce.
3635 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3636 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3638 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3640 gchar *event = sipmsg_find_header(msg, "Event");
3641 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3642 const char *uri,*state;
3643 xmlnode *xn_list;
3644 xmlnode *xn_resource;
3645 xmlnode *xn_instance;
3647 int expires = 0;
3649 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3650 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3652 if (!request)
3654 const gchar *expires_header;
3655 expires_header = sipmsg_find_header(msg, "Expires");
3656 expires = expires_header ? strtol(expires_header, NULL, 10) : 0;
3657 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires);
3660 if (!subscription_state || strstr(subscription_state, "active"))
3662 if (event && strstr(event, "presence"))
3664 process_incoming_notify_presence(sip, msg);
3666 else if (event && strstr(event, "vnd-microsoft-roaming-contacts"))
3668 sipe_process_roaming_contacts(sip, msg, NULL);
3670 else if (event && strstr(event, "vnd-microsoft-roaming-self"))
3672 sipe_process_roaming_self(sip, msg);
3674 else if (event && strstr(event, "vnd-microsoft-roaming-ACL"))
3676 sipe_process_roaming_acl(sip, msg);
3678 else if (event && strstr(event, "presence.wpending"))
3680 sipe_process_incoming_pending(sip, msg);
3682 else
3684 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3688 //The server sends a (BE)NOTIFY with the status 'terminated'
3689 if(request && subscription_state && strstr(subscription_state, "terminated") )
3691 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3692 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3693 g_free(from);
3696 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3697 if (request && !benotify)
3699 sipmsg_remove_header(msg, "Expires");
3700 sipmsg_remove_header(msg, "subscription-state");
3701 sipmsg_remove_header(msg, "Event");
3702 sipmsg_remove_header(msg, "Require");
3703 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3708 * unused. Needed?
3710 static gchar* gen_xpidf(struct sipe_account_data *sip)
3712 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3713 "<presence>\r\n"
3714 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3715 "<display name=\"sip:%s\"/>\r\n"
3716 "<atom id=\"1234\">\r\n"
3717 "<address uri=\"sip:%s\">\r\n"
3718 "<status status=\"%s\"/>\r\n"
3719 "</address>\r\n"
3720 "</atom>\r\n"
3721 "</presence>\r\n",
3722 sip->username,
3723 sip->username,
3724 sip->username,
3725 sip->status);
3726 return doc;
3731 static gchar* gen_pidf(struct sipe_account_data *sip)
3733 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3734 "<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"
3735 "<tuple id=\"0\">\r\n"
3736 "<status>\r\n"
3737 "<basic>open</basic>\r\n"
3738 "<ep:activities>\r\n"
3739 " <ep:activity>%s</ep:activity>\r\n"
3740 "</ep:activities>"
3741 "</status>\r\n"
3742 "</tuple>\r\n"
3743 "<ci:display-name>%s</ci:display-name>\r\n"
3744 "</presence>",
3745 sip->username,
3746 sip->status,
3747 sip->username);
3748 return doc;
3752 static gboolean
3753 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3755 if (msg->response == 488) {
3756 sip->presence_method_version = 1;
3757 send_presence_info(sip);
3759 return TRUE;
3762 static void send_presence_info_v0(struct sipe_account_data *sip, const char * note)
3764 int availability = 300; // online
3765 int activity = 400; // Available
3766 gchar *name;
3767 gchar *body;
3768 if (!strcmp(sip->status, "away")) {
3769 activity = 100;
3770 } else if (!strcmp(sip->status, "out-to-lunch")) {
3771 activity = 150;
3772 } else if (!strcmp(sip->status, "be-right-back")) {
3773 activity = 300;
3774 } else if (!strcmp(sip->status, "on-the-phone")) {
3775 activity = 500;
3776 } else if (!strcmp(sip->status, "do-not-disturb")) {
3777 activity = 600;
3778 } else if (!strcmp(sip->status, "busy")) {
3779 activity = 600;
3780 } else if (!strcmp(sip->status, "invisible")) {
3781 availability = 0; // offline
3782 activity = 100;
3785 name = g_strdup_printf("sip: sip:%s", sip->username);
3786 //@TODO: send user data - state; add hostname in upper case
3787 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3788 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
3789 g_free(name);
3790 g_free(body);
3793 static gboolean
3794 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3796 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3797 if (msg->response == 200) {
3798 sip->status_version = 0;
3799 send_presence_info(sip);
3801 return TRUE;
3804 static gboolean
3805 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3807 if (msg->response == 409) {
3808 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3809 // TODO need to parse the version #'s?
3810 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3811 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3812 gchar *tmp;
3813 gchar *hdr;
3815 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg->body);
3817 tmp = get_contact(sip);
3818 hdr = g_strdup_printf("Contact: %s\r\n"
3819 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3821 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3823 g_free(tmp);
3824 g_free(hdr);
3825 g_free(uri);
3826 g_free(doc);
3828 return TRUE;
3831 static void send_presence_info_v1(struct sipe_account_data *sip, const char * note)
3833 int code;
3834 gchar *uri;
3835 gchar *doc;
3836 gchar *tmp;
3837 gchar *hdr;
3838 if (!strcmp(sip->status, "away")) {
3839 code = 12000;
3840 } else if (!strcmp(sip->status, "busy")) {
3841 code = 6000;
3842 } else {
3843 // Available
3844 code = 3000;
3847 uri = g_strdup_printf("sip:%s", sip->username);
3848 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3849 sip->status_version, code,
3850 sip->status_version, code,
3851 sip->status_version, note ? note : "",
3852 sip->status_version, note ? note : "",
3853 sip->status_version, note ? note : ""
3855 sip->status_version++;
3857 tmp = get_contact(sip);
3858 hdr = g_strdup_printf("Contact: %s\r\n"
3859 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3861 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
3863 g_free(tmp);
3864 g_free(hdr);
3865 g_free(uri);
3866 g_free(doc);
3869 static void send_presence_info(struct sipe_account_data *sip)
3871 PurpleStatus * status = purple_account_get_active_status(sip->account);
3872 const gchar *note;
3873 if (!status) return;
3875 note = purple_status_get_attr_string(status, "message");
3877 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
3878 if (sip->presence_method_version != 1) {
3879 send_presence_info_v0(sip, note);
3880 } else {
3881 send_presence_info_v1(sip, note);
3885 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3887 gboolean found = FALSE;
3888 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3889 if (msg->response == 0) { /* request */
3890 if (!strcmp(msg->method, "MESSAGE")) {
3891 process_incoming_message(sip, msg);
3892 found = TRUE;
3893 } else if (!strcmp(msg->method, "NOTIFY")) {
3894 purple_debug_info("sipe","send->process_incoming_notify\n");
3895 process_incoming_notify(sip, msg, TRUE, FALSE);
3896 found = TRUE;
3897 } else if (!strcmp(msg->method, "BENOTIFY")) {
3898 purple_debug_info("sipe","send->process_incoming_benotify\n");
3899 process_incoming_notify(sip, msg, TRUE, TRUE);
3900 found = TRUE;
3901 } else if (!strcmp(msg->method, "INVITE")) {
3902 process_incoming_invite(sip, msg);
3903 found = TRUE;
3904 } else if (!strcmp(msg->method, "OPTIONS")) {
3905 process_incoming_options(sip, msg);
3906 found = TRUE;
3907 } else if (!strcmp(msg->method, "INFO")) {
3908 // TODO needs work
3909 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3910 if (from) {
3911 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3913 g_free(from);
3914 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3915 found = TRUE;
3916 } else if (!strcmp(msg->method, "ACK")) {
3917 // ACK's don't need any response
3918 found = TRUE;
3919 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3920 // LCS 2005 sends us these - just respond 200 OK
3921 found = TRUE;
3922 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3923 } else if (!strcmp(msg->method, "BYE")) {
3924 struct sip_im_session *session;
3925 gchar *from;
3926 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3928 from = parse_from(sipmsg_find_header(msg, "From"));
3929 session = find_im_session (sip, from);
3930 g_free(from);
3932 if (session) {
3933 // TODO Let the user know the other user left the conversation?
3934 im_session_destroy(sip, session);
3937 found = TRUE;
3938 } else {
3939 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3941 } else { /* response */
3942 struct transaction *trans = transactions_find(sip, msg);
3943 if (trans) {
3944 if (msg->response == 407) {
3945 gchar *resend, *auth, *ptmp;
3947 if (sip->proxy.retries > 30) return;
3948 sip->proxy.retries++;
3949 /* do proxy authentication */
3951 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3953 fill_auth(sip, ptmp, &sip->proxy);
3954 auth = auth_header(sip, &sip->proxy, trans->msg);
3955 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3956 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
3957 g_free(auth);
3958 resend = sipmsg_to_string(trans->msg);
3959 /* resend request */
3960 sendout_pkt(sip->gc, resend);
3961 g_free(resend);
3962 } else {
3963 if (msg->response == 100 || msg->response == 180) {
3964 /* ignore provisional response */
3965 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
3966 } else {
3967 sip->proxy.retries = 0;
3968 if (!strcmp(trans->msg->method, "REGISTER")) {
3969 if (msg->response == 401)
3971 sip->registrar.retries++;
3972 sip->registrar.expires = 0;
3974 else
3976 sip->registrar.retries = 0;
3978 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
3979 } else {
3980 if (msg->response == 401) {
3981 gchar *resend, *auth, *ptmp;
3983 if (sip->registrar.retries > 4) return;
3984 sip->registrar.retries++;
3986 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3987 ptmp = sipmsg_find_auth_header(msg, "NTLM");
3988 } else {
3989 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
3992 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
3994 fill_auth(sip, ptmp, &sip->registrar);
3995 auth = auth_header(sip, &sip->registrar, trans->msg);
3996 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3997 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
3999 //sipmsg_remove_header(trans->msg, "Authorization");
4000 //sipmsg_add_header(trans->msg, "Authorization", auth);
4001 g_free(auth);
4002 resend = sipmsg_to_string(trans->msg);
4003 /* resend request */
4004 sendout_pkt(sip->gc, resend);
4005 g_free(resend);
4009 if (trans->callback) {
4010 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4011 /* call the callback to process response*/
4012 (trans->callback)(sip, msg, trans);
4014 /* Not sure if this is needed or what needs to be done
4015 but transactions seem to be removed prematurely so
4016 this only removes them if the response is 200 OK */
4017 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4018 /*Has a bug and it's unneccesary*/
4019 /*transactions_remove(sip, trans);*/
4023 found = TRUE;
4024 } else {
4025 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
4028 if (!found) {
4029 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4033 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4035 char *cur;
4036 char *dummy;
4037 struct sipmsg *msg;
4038 int restlen;
4039 cur = conn->inbuf;
4041 /* according to the RFC remove CRLF at the beginning */
4042 while (*cur == '\r' || *cur == '\n') {
4043 cur++;
4045 if (cur != conn->inbuf) {
4046 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4047 conn->inbufused = strlen(conn->inbuf);
4050 /* Received a full Header? */
4051 sip->processing_input = TRUE;
4052 while (sip->processing_input &&
4053 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4054 time_t currtime = time(NULL);
4055 cur += 2;
4056 cur[0] = '\0';
4057 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4058 msg = sipmsg_parse_header(conn->inbuf);
4059 cur[0] = '\r';
4060 cur += 2;
4061 restlen = conn->inbufused - (cur - conn->inbuf);
4062 if (restlen >= msg->bodylen) {
4063 dummy = g_malloc(msg->bodylen + 1);
4064 memcpy(dummy, cur, msg->bodylen);
4065 dummy[msg->bodylen] = '\0';
4066 msg->body = dummy;
4067 cur += msg->bodylen;
4068 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4069 conn->inbufused = strlen(conn->inbuf);
4070 } else {
4071 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4072 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4073 sipmsg_free(msg);
4074 return;
4077 /*if (msg->body) {
4078 purple_debug_info("sipe", "body:\n%s", msg->body);
4081 // Verify the signature before processing it
4082 if (sip->registrar.ntlm_key) {
4083 struct sipmsg_breakdown msgbd;
4084 gchar *signature_input_str;
4085 gchar *signature = NULL;
4086 gchar *rspauth;
4087 msgbd.msg = msg;
4088 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4089 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4090 if (signature_input_str != NULL) {
4091 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4093 g_free(signature_input_str);
4095 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4097 if (signature != NULL) {
4098 if (rspauth != NULL) {
4099 if (purple_ntlm_verify_signature (signature, rspauth)) {
4100 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4101 process_input_message(sip, msg);
4102 } else {
4103 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4104 purple_connection_error(sip->gc, _("Invalid message signature received"));
4105 sip->gc->wants_to_die = TRUE;
4107 } else if (msg->response == 401) {
4108 purple_connection_error(sip->gc, _("Wrong Password"));
4109 sip->gc->wants_to_die = TRUE;
4111 g_free(signature);
4114 g_free(rspauth);
4115 sipmsg_breakdown_free(&msgbd);
4116 } else {
4117 process_input_message(sip, msg);
4120 sipmsg_free(msg);
4124 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4126 PurpleConnection *gc = data;
4127 struct sipe_account_data *sip = gc->proto_data;
4128 struct sipmsg *msg;
4129 int len;
4130 time_t currtime;
4132 static char buffer[65536];
4133 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4134 buffer[len] = '\0';
4135 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4136 msg = sipmsg_parse_msg(buffer);
4137 if (msg) process_input_message(sip, msg);
4141 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4143 struct sipe_account_data *sip = gc->proto_data;
4144 PurpleSslConnection *gsc = sip->gsc;
4146 purple_debug_error("sipe", "%s",debug);
4147 purple_connection_error(gc, msg);
4149 /* Invalidate this connection. Next send will open a new one */
4150 if (gsc) {
4151 connection_remove(sip, gsc->fd);
4152 purple_ssl_close(gsc);
4154 sip->gsc = NULL;
4155 sip->fd = -1;
4158 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4160 PurpleConnection *gc = data;
4161 struct sipe_account_data *sip;
4162 struct sip_connection *conn;
4163 int readlen, len;
4164 gboolean firstread = TRUE;
4166 /* NOTE: This check *IS* necessary */
4167 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4168 purple_ssl_close(gsc);
4169 return;
4172 sip = gc->proto_data;
4173 conn = connection_find(sip, gsc->fd);
4174 if (conn == NULL) {
4175 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4176 gc->wants_to_die = TRUE;
4177 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4178 return;
4181 /* Read all available data from the SSL connection */
4182 do {
4183 /* Increase input buffer size as needed */
4184 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4185 conn->inbuflen += SIMPLE_BUF_INC;
4186 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4187 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4190 /* Try to read as much as there is space left in the buffer */
4191 readlen = conn->inbuflen - conn->inbufused - 1;
4192 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4194 if (len < 0 && errno == EAGAIN) {
4195 /* Try again later */
4196 return;
4197 } else if (len < 0) {
4198 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4199 return;
4200 } else if (firstread && (len == 0)) {
4201 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4202 return;
4205 conn->inbufused += len;
4206 firstread = FALSE;
4208 /* Equivalence indicates that there is possibly more data to read */
4209 } while (len == readlen);
4211 conn->inbuf[conn->inbufused] = '\0';
4212 process_input(sip, conn);
4216 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4218 PurpleConnection *gc = data;
4219 struct sipe_account_data *sip = gc->proto_data;
4220 int len;
4221 struct sip_connection *conn = connection_find(sip, source);
4222 if (!conn) {
4223 purple_debug_error("sipe", "Connection not found!\n");
4224 return;
4227 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4228 conn->inbuflen += SIMPLE_BUF_INC;
4229 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4232 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4234 if (len < 0 && errno == EAGAIN)
4235 return;
4236 else if (len <= 0) {
4237 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4238 connection_remove(sip, source);
4239 if (sip->fd == source) sip->fd = -1;
4240 return;
4243 conn->inbufused += len;
4244 conn->inbuf[conn->inbufused] = '\0';
4246 process_input(sip, conn);
4249 /* Callback for new connections on incoming TCP port */
4250 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4252 PurpleConnection *gc = data;
4253 struct sipe_account_data *sip = gc->proto_data;
4254 struct sip_connection *conn;
4256 int newfd = accept(source, NULL, NULL);
4258 conn = connection_create(sip, newfd);
4260 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4263 static void login_cb(gpointer data, gint source, const gchar *error_message)
4265 PurpleConnection *gc = data;
4266 struct sipe_account_data *sip;
4267 struct sip_connection *conn;
4269 if (!PURPLE_CONNECTION_IS_VALID(gc))
4271 if (source >= 0)
4272 close(source);
4273 return;
4276 if (source < 0) {
4277 purple_connection_error(gc, _("Could not connect"));
4278 return;
4281 sip = gc->proto_data;
4282 sip->fd = source;
4283 sip->last_keepalive = time(NULL);
4285 conn = connection_create(sip, source);
4287 do_register(sip);
4289 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4292 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4294 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4295 if (sip == NULL) return;
4297 do_register(sip);
4300 static guint sipe_ht_hash_nick(const char *nick)
4302 char *lc = g_utf8_strdown(nick, -1);
4303 guint bucket = g_str_hash(lc);
4304 g_free(lc);
4306 return bucket;
4309 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4311 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4314 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4316 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4318 sip->listen_data = NULL;
4320 if (listenfd == -1) {
4321 purple_connection_error(sip->gc, _("Could not create listen socket"));
4322 return;
4325 sip->fd = listenfd;
4327 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4328 sip->listenfd = sip->fd;
4330 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4332 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4333 do_register(sip);
4336 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4338 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4339 int addr_size;
4341 sip->query_data = NULL;
4343 if (!hosts || !hosts->data) {
4344 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4345 return;
4348 addr_size = GPOINTER_TO_INT(hosts->data);
4349 hosts = g_slist_remove(hosts, hosts->data);
4350 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4351 g_free(hosts->data);
4352 hosts = g_slist_remove(hosts, hosts->data);
4353 while (hosts) {
4354 hosts = g_slist_remove(hosts, hosts->data);
4355 g_free(hosts->data);
4356 hosts = g_slist_remove(hosts, hosts->data);
4359 /* create socket for incoming connections */
4360 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4361 sipe_udp_host_resolved_listen_cb, sip);
4362 if (sip->listen_data == NULL) {
4363 purple_connection_error(sip->gc, _("Could not create listen socket"));
4364 return;
4368 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4369 gpointer data)
4371 PurpleConnection *gc = data;
4372 struct sipe_account_data *sip;
4374 /* If the connection is already disconnected, we don't need to do anything else */
4375 if (!PURPLE_CONNECTION_IS_VALID(gc))
4376 return;
4378 sip = gc->proto_data;
4379 sip->fd = -1;
4380 sip->gsc = NULL;
4382 switch(error) {
4383 case PURPLE_SSL_CONNECT_FAILED:
4384 purple_connection_error(gc, _("Connection Failed"));
4385 break;
4386 case PURPLE_SSL_HANDSHAKE_FAILED:
4387 purple_connection_error(gc, _("SSL Handshake Failed"));
4388 break;
4389 case PURPLE_SSL_CERTIFICATE_INVALID:
4390 purple_connection_error(gc, _("SSL Certificate Invalid"));
4391 break;
4395 static void
4396 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4398 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4399 PurpleProxyConnectData *connect_data;
4401 sip->listen_data = NULL;
4403 sip->listenfd = listenfd;
4404 if (sip->listenfd == -1) {
4405 purple_connection_error(sip->gc, _("Could not create listen socket"));
4406 return;
4409 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4410 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4411 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4412 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4413 sipe_newconn_cb, sip->gc);
4414 purple_debug_info("sipe", "connecting to %s port %d\n",
4415 sip->realhostname, sip->realport);
4416 /* open tcp connection to the server */
4417 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4418 sip->realport, login_cb, sip->gc);
4420 if (connect_data == NULL) {
4421 purple_connection_error(sip->gc, _("Couldn't create socket"));
4426 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4428 PurpleAccount *account = sip->account;
4429 PurpleConnection *gc = sip->gc;
4431 if (purple_account_get_bool(account, "useport", FALSE)) {
4432 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4433 port = purple_account_get_int(account, "port", 0);
4434 } else {
4435 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4438 sip->realhostname = hostname;
4439 sip->realport = port;
4441 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4442 hostname, port);
4444 /* TODO: is there a good default grow size? */
4445 if (sip->transport != SIPE_TRANSPORT_UDP)
4446 sip->txbuf = purple_circ_buffer_new(0);
4448 if (sip->transport == SIPE_TRANSPORT_TLS) {
4449 /* SSL case */
4450 if (!purple_ssl_is_supported()) {
4451 gc->wants_to_die = TRUE;
4452 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4453 return;
4456 purple_debug_info("sipe", "using SSL\n");
4458 sip->gsc = purple_ssl_connect(account, hostname, port,
4459 login_cb_ssl, sipe_ssl_connect_failure, gc);
4460 if (sip->gsc == NULL) {
4461 purple_connection_error(gc, _("Could not create SSL context"));
4462 return;
4464 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4465 /* UDP case */
4466 purple_debug_info("sipe", "using UDP\n");
4468 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4469 if (sip->query_data == NULL) {
4470 purple_connection_error(gc, _("Could not resolve hostname"));
4472 } else {
4473 /* TCP case */
4474 purple_debug_info("sipe", "using TCP\n");
4475 /* create socket for incoming connections */
4476 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4477 sipe_tcp_connect_listen_cb, sip);
4478 if (sip->listen_data == NULL) {
4479 purple_connection_error(gc, _("Could not create listen socket"));
4480 return;
4485 /* Service list for autodection */
4486 static const struct sipe_service_data service_autodetect[] = {
4487 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4488 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4489 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4490 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4491 { NULL, NULL, 0 }
4494 /* Service list for SSL/TLS */
4495 static const struct sipe_service_data service_tls[] = {
4496 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4497 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4498 { NULL, NULL, 0 }
4501 /* Service list for TCP */
4502 static const struct sipe_service_data service_tcp[] = {
4503 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4504 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4505 { NULL, NULL, 0 }
4508 /* Service list for UDP */
4509 static const struct sipe_service_data service_udp[] = {
4510 { "sip", "udp", SIPE_TRANSPORT_UDP },
4511 { NULL, NULL, 0 }
4514 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4515 static void resolve_next_service(struct sipe_account_data *sip,
4516 const struct sipe_service_data *start)
4518 if (start) {
4519 sip->service_data = start;
4520 } else {
4521 sip->service_data++;
4522 if (sip->service_data->service == NULL) {
4523 gchar *hostname;
4524 /* Try connecting to the SIP hostname directly */
4525 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4526 if (sip->auto_transport) {
4527 // If SSL is supported, default to using it; OCS servers aren't configured
4528 // by default to accept TCP
4529 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4530 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4531 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4534 hostname = g_strdup(sip->sipdomain);
4535 create_connection(sip, hostname, 0);
4536 return;
4540 /* Try to resolve next service */
4541 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4542 sip->service_data->transport,
4543 sip->sipdomain,
4544 srvresolved, sip);
4547 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4549 struct sipe_account_data *sip = data;
4551 sip->srv_query_data = NULL;
4553 /* find the host to connect to */
4554 if (results) {
4555 gchar *hostname = g_strdup(resp->hostname);
4556 int port = resp->port;
4557 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4558 hostname, port);
4559 g_free(resp);
4561 sip->transport = sip->service_data->type;
4563 create_connection(sip, hostname, port);
4564 } else {
4565 resolve_next_service(sip, NULL);
4569 static void sipe_login(PurpleAccount *account)
4571 PurpleConnection *gc;
4572 struct sipe_account_data *sip;
4573 gchar **signinname_login, **userserver, **domain_user;
4574 const char *transport;
4576 const char *username = purple_account_get_username(account);
4577 gc = purple_account_get_connection(account);
4579 if (strpbrk(username, " \t\v\r\n") != NULL) {
4580 gc->wants_to_die = TRUE;
4581 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4582 return;
4585 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4586 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4587 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4588 sip->gc = gc;
4589 sip->account = account;
4590 sip->registerexpire = 900;
4591 sip->reregister_set = FALSE;
4592 sip->reauthenticate_set = FALSE;
4593 sip->subscribed = FALSE;
4594 sip->subscribed_buddies = FALSE;
4596 signinname_login = g_strsplit(username, ",", 2);
4598 userserver = g_strsplit(signinname_login[0], "@", 2);
4599 purple_connection_set_display_name(gc, userserver[0]);
4600 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4601 sip->sipdomain = g_strdup(userserver[1]);
4603 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4604 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4605 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4607 sip->password = g_strdup(purple_connection_get_password(gc));
4609 g_strfreev(userserver);
4610 g_strfreev(domain_user);
4611 g_strfreev(signinname_login);
4613 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4615 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4617 /* TODO: Set the status correctly. */
4618 sip->status = g_strdup("available");
4620 transport = purple_account_get_string(account, "transport", "auto");
4621 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4622 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4623 SIPE_TRANSPORT_UDP;
4625 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4626 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4627 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4628 } else if (strcmp(transport, "auto") == 0) {
4629 sip->auto_transport = TRUE;
4630 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4631 } else if (strcmp(transport, "tls") == 0) {
4632 resolve_next_service(sip, service_tls);
4633 } else if (strcmp(transport, "tcp") == 0) {
4634 resolve_next_service(sip, service_tcp);
4635 } else {
4636 resolve_next_service(sip, service_udp);
4640 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4642 connection_free_all(sip);
4644 if (sip->query_data != NULL)
4645 purple_dnsquery_destroy(sip->query_data);
4646 sip->query_data == NULL;
4648 if (sip->srv_query_data != NULL)
4649 purple_srv_cancel(sip->srv_query_data);
4650 sip->srv_query_data = NULL;
4652 if (sip->listen_data != NULL)
4653 purple_network_listen_cancel(sip->listen_data);
4654 sip->listen_data = NULL;
4656 if (sip->gsc != NULL)
4657 purple_ssl_close(sip->gsc);
4658 sip->gsc = NULL;
4660 sipe_auth_free(&sip->registrar);
4661 sipe_auth_free(&sip->proxy);
4663 if (sip->txbuf)
4664 purple_circ_buffer_destroy(sip->txbuf);
4665 sip->txbuf = NULL;
4667 g_free(sip->realhostname);
4668 sip->realhostname = NULL;
4670 if (sip->listenpa)
4671 purple_input_remove(sip->listenpa);
4672 sip->listenpa = 0;
4673 if (sip->tx_handler)
4674 purple_input_remove(sip->tx_handler);
4675 sip->tx_handler = 0;
4676 if (sip->resendtimeout)
4677 purple_timeout_remove(sip->resendtimeout);
4678 sip->resendtimeout = 0;
4679 if (sip->timeouts) {
4680 GSList *entry = sip->timeouts;
4681 while (entry) {
4682 struct scheduled_action *sched_action = entry->data;
4683 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4684 purple_timeout_remove(sched_action->timeout_handler);
4685 g_free(sched_action->payload);
4686 g_free(sched_action->name);
4687 g_free(sched_action);
4688 entry = entry->next;
4691 g_slist_free(sip->timeouts);
4693 g_slist_free(sip->allow_events);
4695 if (sip->contact)
4696 g_free(sip->contact);
4697 sip->contact = NULL;
4698 if (sip->regcallid)
4699 g_free(sip->regcallid);
4700 sip->regcallid = NULL;
4702 sip->fd = -1;
4703 sip->processing_input = FALSE;
4707 * A callback for g_hash_table_foreach_remove
4709 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4711 sipe_free_buddy(buddy);
4714 static void sipe_close(PurpleConnection *gc)
4716 struct sipe_account_data *sip = gc->proto_data;
4718 if (sip) {
4719 /* leave all conversations */
4720 im_session_close_all(sip);
4722 /* unregister */
4723 do_register_exp(sip, 0);
4725 sipe_connection_cleanup(sip);
4726 g_free(sip->sipdomain);
4727 g_free(sip->username);
4728 g_free(sip->password);
4729 g_free(sip->authdomain);
4730 g_free(sip->authuser);
4731 g_free(sip->status);
4733 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4734 g_hash_table_destroy(sip->buddies);
4736 g_free(gc->proto_data);
4737 gc->proto_data = NULL;
4740 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4742 PurpleAccount *acct = purple_connection_get_account(gc);
4743 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4744 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4745 if (conv == NULL)
4746 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4747 purple_conversation_present(conv);
4748 g_free(id);
4751 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4754 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4755 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4758 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4760 PurpleNotifySearchResults *results;
4761 PurpleNotifySearchColumn *column;
4762 xmlnode *searchResults;
4763 xmlnode *mrow;
4764 int match_count = 0;
4765 gboolean more = FALSE;
4766 gchar *secondary;
4768 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
4770 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4771 if (!searchResults) {
4772 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4773 return FALSE;
4776 results = purple_notify_searchresults_new();
4778 if (results == NULL) {
4779 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4780 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4782 xmlnode_free(searchResults);
4783 return FALSE;
4786 column = purple_notify_searchresults_column_new(_("User Name"));
4787 purple_notify_searchresults_column_add(results, column);
4789 column = purple_notify_searchresults_column_new(_("Name"));
4790 purple_notify_searchresults_column_add(results, column);
4792 column = purple_notify_searchresults_column_new(_("Company"));
4793 purple_notify_searchresults_column_add(results, column);
4795 column = purple_notify_searchresults_column_new(_("Country"));
4796 purple_notify_searchresults_column_add(results, column);
4798 column = purple_notify_searchresults_column_new(_("Email"));
4799 purple_notify_searchresults_column_add(results, column);
4801 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4802 GList *row = NULL;
4804 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4805 row = g_list_append(row, g_strdup(uri_parts[1]));
4806 g_strfreev(uri_parts);
4808 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4809 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4810 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4811 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4813 purple_notify_searchresults_row_add(results, row);
4814 match_count++;
4817 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4818 char *data = xmlnode_get_data_unescaped(mrow);
4819 more = (g_strcasecmp(data, "true") == 0);
4820 g_free(data);
4823 secondary = g_strdup_printf(
4824 dngettext(GETTEXT_PACKAGE,
4825 "Found %d contact%s:",
4826 "Found %d contacts%s:", match_count),
4827 match_count, more ? _(" (more matched your query)") : "");
4829 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4830 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4831 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4833 g_free(secondary);
4834 xmlnode_free(searchResults);
4835 return TRUE;
4838 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4840 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4841 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4842 unsigned i = 0;
4844 do {
4845 PurpleRequestField *field = entries->data;
4846 const char *id = purple_request_field_get_id(field);
4847 const char *value = purple_request_field_string_get_value(field);
4849 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
4851 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
4852 } while ((entries = g_list_next(entries)) != NULL);
4853 attrs[i] = NULL;
4855 if (i > 0) {
4856 gchar *query = g_strjoinv(NULL, attrs);
4857 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
4858 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
4859 send_soap_request_with_cb(gc->proto_data, body,
4860 (TransCallback) process_search_contact_response, NULL);
4861 g_free(body);
4862 g_free(query);
4865 g_strfreev(attrs);
4868 static void sipe_show_find_contact(PurplePluginAction *action)
4870 PurpleConnection *gc = (PurpleConnection *) action->context;
4871 PurpleRequestFields *fields;
4872 PurpleRequestFieldGroup *group;
4873 PurpleRequestField *field;
4875 fields = purple_request_fields_new();
4876 group = purple_request_field_group_new(NULL);
4877 purple_request_fields_add_group(fields, group);
4879 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4880 purple_request_field_group_add_field(group, field);
4881 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4882 purple_request_field_group_add_field(group, field);
4883 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4884 purple_request_field_group_add_field(group, field);
4885 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4886 purple_request_field_group_add_field(group, field);
4888 purple_request_fields(gc,
4889 _("Search"),
4890 _("Search for a Contact"),
4891 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4892 fields,
4893 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4894 _("_Cancel"), NULL,
4895 purple_connection_get_account(gc), NULL, NULL, gc);
4898 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4900 GList *menu = NULL;
4901 PurplePluginAction *act;
4903 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4904 menu = g_list_prepend(menu, act);
4906 menu = g_list_reverse(menu);
4908 return menu;
4911 static void dummy_permit_deny(PurpleConnection *gc)
4915 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4917 return TRUE;
4921 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4923 return TRUE;
4927 static char *sipe_status_text(PurpleBuddy *buddy)
4929 struct sipe_account_data *sip;
4930 struct sipe_buddy *sbuddy;
4931 char *text = NULL;
4933 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4934 if (sip) //happens on pidgin exit
4936 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4937 if (sbuddy && sbuddy->annotation)
4939 text = g_strdup(sbuddy->annotation);
4943 return text;
4946 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4948 const PurplePresence *presence = purple_buddy_get_presence(buddy);
4949 const PurpleStatus *status = purple_presence_get_active_status(presence);
4950 struct sipe_account_data *sip;
4951 struct sipe_buddy *sbuddy;
4952 char *annotation = NULL;
4954 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4955 if (sip) //happens on pidgin exit
4957 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4958 if (sbuddy)
4960 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
4964 //Layout
4965 if (purple_presence_is_online(presence))
4967 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
4970 if (annotation)
4972 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
4973 g_free(annotation);
4978 static GHashTable *
4979 sipe_get_account_text_table(PurpleAccount *account)
4981 GHashTable *table;
4982 table = g_hash_table_new(g_str_hash, g_str_equal);
4983 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
4984 return table;
4987 static PurpleBuddy *
4988 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
4990 PurpleBuddy *clone;
4991 const gchar *server_alias, *email;
4992 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
4994 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
4996 purple_blist_add_buddy(clone, NULL, group, NULL);
4998 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
4999 if (server_alias) {
5000 purple_blist_server_alias_buddy(clone, server_alias);
5003 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5004 if (email) {
5005 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5008 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5009 //for UI to update;
5010 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5011 return clone;
5014 static void
5015 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5017 PurpleBuddy *buddy, *b;
5018 PurpleConnection *gc;
5019 PurpleGroup * group = purple_find_group(group_name);
5021 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5023 buddy = (PurpleBuddy *)node;
5025 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5026 gc = purple_account_get_connection(buddy->account);
5028 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5029 if (!b){
5030 b = purple_blist_add_buddy_clone(group, buddy);
5033 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5036 static void
5037 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5039 const gchar *email;
5040 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5042 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5043 if (email)
5045 char *mailto = g_strdup_printf("mailto:%s", email);
5046 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5047 #ifndef _WIN32
5049 pid_t pid;
5050 char *const parmList[] = {mailto, NULL};
5051 if ((pid = fork()) == -1)
5053 purple_debug_info("sipe", "fork() error\n");
5055 else if (pid == 0)
5057 execvp("xdg-email", parmList);
5058 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5061 #else
5063 BOOL ret;
5064 _flushall();
5065 errno = 0;
5066 //@TODO resolve env variable %WINDIR% first
5067 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5068 if (errno)
5070 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5073 #endif
5075 g_free(mailto);
5077 else
5079 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5084 * A menu which appear when right-clicking on buddy in contact list.
5086 static GList *
5087 sipe_buddy_menu(PurpleBuddy *buddy)
5089 PurpleBlistNode *g_node;
5090 PurpleGroup *group, *gr_parent;
5091 PurpleMenuAction *act;
5092 GList *menu = NULL;
5093 GList *menu_groups = NULL;
5095 act = purple_menu_action_new(_("Send Email..."),
5096 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5097 NULL, NULL);
5098 menu = g_list_prepend(menu, act);
5100 gr_parent = purple_buddy_get_group(buddy);
5101 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5102 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5103 continue;
5105 group = (PurpleGroup *)g_node;
5106 if (group == gr_parent)
5107 continue;
5109 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5110 continue;
5112 act = purple_menu_action_new(purple_group_get_name(group),
5113 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5114 group->name, NULL);
5115 menu_groups = g_list_prepend(menu_groups, act);
5117 menu_groups = g_list_reverse(menu_groups);
5119 act = purple_menu_action_new(_("Copy to"),
5120 NULL,
5121 NULL, menu_groups);
5122 menu = g_list_prepend(menu, act);
5123 menu = g_list_reverse(menu);
5125 return menu;
5128 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5129 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5130 return sipe_buddy_menu((PurpleBuddy *) node);
5131 } else {
5132 return NULL;
5136 static gboolean
5137 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5139 gboolean ret = TRUE;
5140 char *username = (char *)trans->payload;
5142 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5143 PurpleBuddy *pbuddy;
5144 struct sipe_buddy *sbuddy;
5145 const char *alias;
5146 char *server_alias = NULL;
5147 char *email = NULL;
5148 const char *device_name = NULL;
5150 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5152 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5153 alias = purple_buddy_get_local_alias(pbuddy);
5155 if (sip)
5157 //will query buddy UA's capabilities and send answer to log
5158 sipe_options_request(sip, username);
5160 sbuddy = g_hash_table_lookup(sip->buddies, username);
5161 if (sbuddy)
5163 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5167 if (msg->response != 200) {
5168 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5169 } else {
5170 xmlnode *searchResults;
5171 xmlnode *mrow;
5173 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5174 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5175 if (!searchResults) {
5176 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5177 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5178 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5179 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5180 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5181 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5182 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5183 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5184 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5185 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5186 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5187 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5188 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5189 if (!email || strcmp("", email)) {
5190 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5191 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5195 xmlnode_free(searchResults);
5198 purple_notify_user_info_add_section_break(info);
5200 if (!server_alias || !strcmp("", server_alias)) {
5201 g_free(server_alias);
5202 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5203 if (server_alias) {
5204 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5208 // same as server alias, do not present
5209 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5210 if (alias)
5212 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5215 if (!email || !strcmp("", email)) {
5216 g_free(email);
5217 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5218 if (email) {
5219 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5223 if (device_name)
5225 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5228 /* show a buddy's user info in a nice dialog box */
5229 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5230 username, /* buddy's username */
5231 info, /* body */
5232 NULL, /* callback called when dialog closed */
5233 NULL); /* userdata for callback */
5235 return ret;
5239 * AD search first, LDAP based
5241 static void sipe_get_info(PurpleConnection *gc, const char *username)
5243 char *row = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5244 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5246 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5247 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5248 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5249 g_free(body);
5250 g_free(row);
5253 static PurplePlugin *my_protocol = NULL;
5255 static PurplePluginProtocolInfo prpl_info =
5258 NULL, /* user_splits */
5259 NULL, /* protocol_options */
5260 NO_BUDDY_ICONS, /* icon_spec */
5261 sipe_list_icon, /* list_icon */
5262 NULL, /* list_emblems */
5263 sipe_status_text, /* status_text */
5264 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5265 sipe_status_types, /* away_states */
5266 sipe_blist_node_menu, /* blist_node_menu */
5267 NULL, /* chat_info */
5268 NULL, /* chat_info_defaults */
5269 sipe_login, /* login */
5270 sipe_close, /* close */
5271 sipe_im_send, /* send_im */
5272 NULL, /* set_info */ // TODO maybe
5273 sipe_send_typing, /* send_typing */
5274 sipe_get_info, /* get_info */
5275 sipe_set_status, /* set_status */
5276 NULL, /* set_idle */
5277 NULL, /* change_passwd */
5278 sipe_add_buddy, /* add_buddy */
5279 NULL, /* add_buddies */
5280 sipe_remove_buddy, /* remove_buddy */
5281 NULL, /* remove_buddies */
5282 sipe_add_permit, /* add_permit */
5283 sipe_add_deny, /* add_deny */
5284 sipe_add_deny, /* rem_permit */
5285 sipe_add_permit, /* rem_deny */
5286 dummy_permit_deny, /* set_permit_deny */
5287 NULL, /* join_chat */
5288 NULL, /* reject_chat */
5289 NULL, /* get_chat_name */
5290 NULL, /* chat_invite */
5291 NULL, /* chat_leave */
5292 NULL, /* chat_whisper */
5293 NULL, /* chat_send */
5294 sipe_keep_alive, /* keepalive */
5295 NULL, /* register_user */
5296 NULL, /* get_cb_info */ // deprecated
5297 NULL, /* get_cb_away */ // deprecated
5298 sipe_alias_buddy, /* alias_buddy */
5299 sipe_group_buddy, /* group_buddy */
5300 sipe_rename_group, /* rename_group */
5301 NULL, /* buddy_free */
5302 sipe_convo_closed, /* convo_closed */
5303 purple_normalize_nocase, /* normalize */
5304 NULL, /* set_buddy_icon */
5305 sipe_remove_group, /* remove_group */
5306 NULL, /* get_cb_real_name */ // TODO?
5307 NULL, /* set_chat_topic */
5308 NULL, /* find_blist_chat */
5309 NULL, /* roomlist_get_list */
5310 NULL, /* roomlist_cancel */
5311 NULL, /* roomlist_expand_category */
5312 NULL, /* can_receive_file */
5313 NULL, /* send_file */
5314 NULL, /* new_xfer */
5315 NULL, /* offline_message */
5316 NULL, /* whiteboard_prpl_ops */
5317 sipe_send_raw, /* send_raw */
5318 NULL, /* roomlist_room_serialize */
5319 NULL, /* unregister_user */
5320 NULL, /* send_attention */
5321 NULL, /* get_attention_types */
5323 sizeof(PurplePluginProtocolInfo), /* struct_size */
5324 sipe_get_account_text_table, /* get_account_text_table */
5328 static PurplePluginInfo info = {
5329 PURPLE_PLUGIN_MAGIC,
5330 PURPLE_MAJOR_VERSION,
5331 PURPLE_MINOR_VERSION,
5332 PURPLE_PLUGIN_PROTOCOL, /**< type */
5333 NULL, /**< ui_requirement */
5334 0, /**< flags */
5335 NULL, /**< dependencies */
5336 PURPLE_PRIORITY_DEFAULT, /**< priority */
5337 "prpl-sipe", /**< id */
5338 "Microsoft LCS/OCS", /**< name */
5339 VERSION, /**< version */
5340 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5341 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5342 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5343 "Gabriel Burt <gburt@novell.com>", /**< author */
5344 PURPLE_WEBSITE, /**< homepage */
5345 sipe_plugin_load, /**< load */
5346 sipe_plugin_unload, /**< unload */
5347 sipe_plugin_destroy, /**< destroy */
5348 NULL, /**< ui_info */
5349 &prpl_info, /**< extra_info */
5350 NULL,
5351 sipe_actions,
5352 NULL,
5353 NULL,
5354 NULL,
5355 NULL
5358 static void sipe_plugin_destroy(PurplePlugin *plugin)
5360 GList *entry;
5362 entry = prpl_info.protocol_options;
5363 while (entry) {
5364 purple_account_option_destroy(entry->data);
5365 entry = g_list_delete_link(entry, entry);
5367 prpl_info.protocol_options = NULL;
5369 entry = prpl_info.user_splits;
5370 while (entry) {
5371 purple_account_user_split_destroy(entry->data);
5372 entry = g_list_delete_link(entry, entry);
5374 prpl_info.user_splits = NULL;
5377 static void init_plugin(PurplePlugin *plugin)
5379 PurpleAccountUserSplit *split;
5380 PurpleAccountOption *option;
5382 #ifdef ENABLE_NLS
5383 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5384 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5385 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5386 #endif
5388 purple_plugin_register(plugin);
5390 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5391 purple_account_user_split_set_reverse(split, FALSE);
5392 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5394 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5395 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5396 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5397 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5399 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5400 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5401 // Translators: noun (networking port)
5402 option = purple_account_option_int_new(_("Port"), "port", 5061);
5403 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5405 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5406 purple_account_option_add_list_item(option, _("Auto"), "auto");
5407 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5408 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5409 purple_account_option_add_list_item(option, _("UDP"), "udp");
5410 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5412 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5413 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5415 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5416 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5418 // TODO commented out so won't show in the preferences until we fix krb message signing
5419 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5420 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5422 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5423 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5424 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5427 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5428 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5429 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5430 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5431 my_protocol = plugin;
5434 /* I had to redefined the function for it load, but works */
5435 gboolean purple_init_plugin(PurplePlugin *plugin){
5436 plugin->info = &(info);
5437 init_plugin((plugin));
5438 sipe_plugin_load((plugin));
5439 return purple_plugin_register(plugin);