Let server decide registration expiration period
[siplcs.git] / src / sipe.c
blob06f26bd79286103b93949a34ab75a18710981c72
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2007 Anibal Avelar <avelar@gmail.com>
8 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
10 * ***
11 * Thanks to Google's Summer of Code Program and the helpful mentors
12 * ***
14 * Session-based SIP MESSAGE documentation:
15 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 #ifndef _WIN32
33 #include <sys/socket.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
36 #include <netinet/in.h>
37 #include <net/if.h>
38 #ifdef ENABLE_NLS
39 # include <libintl.h>
40 # define _(String) ((const char *) gettext (String))
41 #else
42 # define _(String) ((const char *) (String))
43 #endif /* ENABLE_NLS */
44 #else
45 #ifdef _DLL
46 #define _WS2TCPIP_H_
47 #define _WINSOCK2API_
48 #define _LIBC_INTERNAL_
49 #endif /* _DLL */
51 #include "internal.h"
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <glib.h>
61 #include "accountopt.h"
62 #include "blist.h"
63 #include "conversation.h"
64 #include "dnsquery.h"
65 #include "debug.h"
66 #include "notify.h"
67 #include "privacy.h"
68 #include "prpl.h"
69 #include "plugin.h"
70 #include "util.h"
71 #include "version.h"
72 #include "network.h"
73 #include "xmlnode.h"
74 #include "mime.h"
76 #include "sipe.h"
77 #include "sip-ntlm.h"
78 #ifdef USE_KERBEROS
79 #include "sipkrb5.h"
80 #endif /*USE_KERBEROS*/
82 #include "sipmsg.h"
83 #include "sipe-sign.h"
84 #include "dnssrv.h"
85 #include "request.h"
87 /* Keep in sync with sipe_transport_type! */
88 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
89 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
91 static char *gentag()
93 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
96 static gchar *get_epid()
98 return sipe_uuid_get_macaddr();
101 static char *genbranch()
103 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
104 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
105 rand() & 0xFFFF, rand() & 0xFFFF);
108 static char *gencallid()
110 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
111 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
112 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
113 rand() & 0xFFFF, rand() & 0xFFFF);
116 static gchar *find_tag(const gchar *hdr)
118 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
119 if (!tag) {
120 // In case it's at the end and there's no trailing ;
121 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
123 return tag;
127 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
129 return "sipe";
132 static void sipe_plugin_destroy(PurplePlugin *plugin);
134 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
136 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
137 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
138 gpointer data);
140 static void sipe_close(PurpleConnection *gc);
142 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name);
143 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip);
144 static void send_presence_info(struct sipe_account_data *sip);
146 static void sendout_pkt(PurpleConnection *gc, const char *buf);
148 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
150 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
151 if (timeout != NULL) {
152 sscanf(timeout, "%u", &sip->keepalive_timeout);
153 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
154 sip->keepalive_timeout);
156 g_free(timeout);
159 static void sipe_keep_alive(PurpleConnection *gc)
161 struct sipe_account_data *sip = gc->proto_data;
162 if (sip->transport == SIPE_TRANSPORT_UDP) {
163 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
164 gchar buf[2] = {0, 0};
165 purple_debug_info("sipe", "sending keep alive\n");
166 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
167 } else {
168 time_t now = time(NULL);
169 if ((sip->keepalive_timeout > 0) &&
170 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
171 #if PURPLE_VERSION_CHECK(2,4,0)
172 && ((now - gc->last_received) >= sip->keepalive_timeout)
173 #endif
175 purple_debug_info("sipe", "sending keep alive\n");
176 sendout_pkt(gc, "\r\n\r\n");
177 sip->last_keepalive = now;
182 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
184 struct sip_connection *ret = NULL;
185 GSList *entry = sip->openconns;
186 while (entry) {
187 ret = entry->data;
188 if (ret->fd == fd) return ret;
189 entry = entry->next;
191 return NULL;
194 static void sipe_auth_free(struct sip_auth *auth)
196 g_free(auth->nonce);
197 auth->nonce = NULL;
198 g_free(auth->opaque);
199 auth->opaque = NULL;
200 g_free(auth->realm);
201 auth->realm = NULL;
202 g_free(auth->target);
203 auth->target = NULL;
204 g_free(auth->digest_session_key);
205 auth->digest_session_key = NULL;
206 g_free(auth->ntlm_key);
207 auth->ntlm_key = NULL;
208 auth->type = AUTH_TYPE_UNSET;
209 auth->retries = 0;
210 auth->expires = 0;
213 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
215 struct sip_connection *ret = g_new0(struct sip_connection, 1);
216 ret->fd = fd;
217 sip->openconns = g_slist_append(sip->openconns, ret);
218 return ret;
221 static void connection_remove(struct sipe_account_data *sip, int fd)
223 struct sip_connection *conn = connection_find(sip, fd);
224 if (conn) {
225 sip->openconns = g_slist_remove(sip->openconns, conn);
226 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
227 g_free(conn->inbuf);
228 g_free(conn);
232 static void connection_free_all(struct sipe_account_data *sip)
234 struct sip_connection *ret = NULL;
235 GSList *entry = sip->openconns;
236 while (entry) {
237 ret = entry->data;
238 connection_remove(sip, ret->fd);
239 entry = sip->openconns;
243 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
245 const gchar *method = msg->method;
246 const gchar *target = msg->target;
247 gchar noncecount[9];
248 gchar *response;
249 gchar *ret;
250 gchar *tmp = NULL;
251 const char *authdomain = sip->authdomain;
252 const char *authuser = sip->authuser;
253 //const char *krb5_realm;
254 const char *host;
255 //gchar *krb5_token = NULL;
257 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
258 // and do error checking
260 // KRB realm should always be uppercase
261 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
263 if (sip->realhostname) {
264 host = sip->realhostname;
265 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
266 host = purple_account_get_string(sip->account, "proxy", "");
267 } else {
268 host = sip->sipdomain;
271 /*gboolean new_auth = krb5_auth.gss_context == NULL;
272 if (new_auth) {
273 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
276 if (new_auth || force_reauth) {
277 krb5_token = krb5_auth.base64_token;
280 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
281 krb5_token = krb5_auth.base64_token;*/
283 if (!authdomain) {
284 authdomain = "";
287 if (!authuser || strlen(authuser) < 1) {
288 authuser = sip->username;
291 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
292 sprintf(noncecount, "%08d", auth->nc++);
293 response = purple_cipher_http_digest_calculate_response(
294 "md5", method, target, NULL, NULL,
295 auth->nonce, noncecount, NULL, auth->digest_session_key);
296 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
298 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
299 g_free(response);
300 return ret;
301 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
302 // If we have a signature for the message, include that
303 if (msg->signature) {
304 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
305 return tmp;
308 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
309 const gchar *ntlm_key;
310 gchar *gssapi_data;
311 #if GLIB_CHECK_VERSION(2,8,0)
312 const gchar * hostname = g_get_host_name();
313 #else
314 static char hostname[256];
315 int ret = gethostname(hostname, sizeof(hostname));
316 hostname[sizeof(hostname) - 1] = '\0';
317 if (ret == -1 || hostname[0] == '\0') {
318 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
319 g_strerror(errno);
320 strcpy(hostname, "localhost");
322 #endif
323 /*const gchar * hostname = purple_get_host_name();*/
325 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
326 auth->ntlm_key = (gchar *)ntlm_key;
327 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
328 g_free(gssapi_data);
329 return tmp;
332 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
333 return tmp;
334 } else if (auth->type == AUTH_TYPE_KERBEROS) {
335 /* Kerberos */
336 if (auth->nc == 3) {
337 /*if (new_auth || force_reauth) {
338 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
339 if (auth->opaque) {
340 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, krb5_token);
341 } else {
342 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
344 } else {
345 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
346 gchar * mic = "MICTODO";
347 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
348 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
349 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
350 //auth->opaque ? auth->opaque : "", auth->target);
351 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
352 //g_free(mic);
354 return tmp;
356 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
359 sprintf(noncecount, "%08d", auth->nc++);
360 response = purple_cipher_http_digest_calculate_response(
361 "md5", method, target, NULL, NULL,
362 auth->nonce, noncecount, NULL, auth->digest_session_key);
363 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
365 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
366 g_free(response);
367 return ret;
370 static char *parse_attribute(const char *attrname, const char *source)
372 const char *tmp, *tmp2;
373 char *retval = NULL;
374 int len = strlen(attrname);
376 if (!strncmp(source, attrname, len)) {
377 tmp = source + len;
378 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
379 if (tmp2)
380 retval = g_strndup(tmp, tmp2 - tmp);
381 else
382 retval = g_strdup(tmp);
385 return retval;
388 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
390 int i = 0;
391 const char *authuser;
392 char *tmp;
393 gchar **parts;
394 //const char *krb5_realm;
395 //const char *host;
397 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
398 // and do error checking
400 // KRB realm should always be uppercase
401 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
403 if (sip->realhostname) {
404 host = sip->realhostname;
405 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
406 host = purple_account_get_string(sip->account, "proxy", "");
407 } else {
408 host = sip->sipdomain;
411 authuser = sip->authuser;
413 if (!authuser || strlen(authuser) < 1) {
414 authuser = sip->username;
417 if (!hdr) {
418 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
419 return;
422 if (!g_strncasecmp(hdr, "NTLM", 4)) {
423 auth->type = AUTH_TYPE_NTLM;
424 parts = g_strsplit(hdr+5, "\", ", 0);
425 i = 0;
426 while (parts[i]) {
427 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
428 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
429 g_free(auth->nonce);
430 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
431 g_free(tmp);
433 if ((tmp = parse_attribute("targetname=\"",
434 parts[i]))) {
435 g_free(auth->target);
436 auth->target = tmp;
438 else if ((tmp = parse_attribute("realm=\"",
439 parts[i]))) {
440 g_free(auth->realm);
441 auth->realm = tmp;
443 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
444 g_free(auth->opaque);
445 auth->opaque = tmp;
447 i++;
449 g_strfreev(parts);
450 auth->nc = 1;
451 if (!strstr(hdr, "gssapi-data")) {
452 auth->nc = 1;
453 } else {
454 auth->nc = 3;
456 return;
459 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
460 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
461 auth->type = AUTH_TYPE_KERBEROS;
462 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
463 parts = g_strsplit(hdr+9, "\", ", 0);
464 i = 0;
465 while (parts[i]) {
466 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
467 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
468 /*if (krb5_auth.gss_context == NULL) {
469 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
471 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
472 g_free(tmp);
474 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
475 g_free(auth->target);
476 auth->target = tmp;
477 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
478 g_free(auth->realm);
479 auth->realm = tmp;
480 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
481 g_free(auth->opaque);
482 auth->opaque = tmp;
484 i++;
486 g_strfreev(parts);
487 auth->nc = 3;
488 return;
491 auth->type = AUTH_TYPE_DIGEST;
492 parts = g_strsplit(hdr, " ", 0);
493 while (parts[i]) {
494 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
495 g_free(auth->nonce);
496 auth->nonce = tmp;
498 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
499 g_free(auth->realm);
500 auth->realm = tmp;
502 i++;
504 g_strfreev(parts);
506 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
507 if (auth->realm) {
508 g_free(auth->digest_session_key);
509 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
510 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
512 auth->nc = 1;
516 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
518 PurpleConnection *gc = data;
519 struct sipe_account_data *sip = gc->proto_data;
520 gsize max_write;
521 gssize written;
523 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
525 if (max_write == 0) {
526 if (sip->tx_handler != 0){
527 purple_input_remove(sip->tx_handler);
528 sip->tx_handler = 0;
530 return;
533 written = write(sip->fd, sip->txbuf->outptr, max_write);
535 if (written < 0 && errno == EAGAIN)
536 written = 0;
537 else if (written <= 0) {
538 /*TODO: do we really want to disconnect on a failure to write?*/
539 purple_connection_error(gc, _("Could not write"));
540 return;
543 purple_circ_buffer_mark_read(sip->txbuf, written);
546 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
548 PurpleConnection *gc = data;
549 struct sipe_account_data *sip = gc->proto_data;
550 gsize max_write;
551 gssize written;
553 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
555 if (max_write == 0) {
556 if (sip->tx_handler != 0) {
557 purple_input_remove(sip->tx_handler);
558 sip->tx_handler = 0;
559 return;
563 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
565 if (written < 0 && errno == EAGAIN)
566 written = 0;
567 else if (written <= 0) {
568 /*TODO: do we really want to disconnect on a failure to write?*/
569 purple_connection_error(gc, _("Could not write"));
570 return;
573 purple_circ_buffer_mark_read(sip->txbuf, written);
576 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
578 static void send_later_cb(gpointer data, gint source, const gchar *error)
580 PurpleConnection *gc = data;
581 struct sipe_account_data *sip;
582 struct sip_connection *conn;
584 if (!PURPLE_CONNECTION_IS_VALID(gc))
586 if (source >= 0)
587 close(source);
588 return;
591 if (source < 0) {
592 purple_connection_error(gc, _("Could not connect"));
593 return;
596 sip = gc->proto_data;
597 sip->fd = source;
598 sip->connecting = FALSE;
599 sip->last_keepalive = time(NULL);
601 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
603 /* If there is more to write now, we need to register a handler */
604 if (sip->txbuf->bufused > 0)
605 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
607 conn = connection_create(sip, source);
608 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
611 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
613 struct sipe_account_data *sip;
614 struct sip_connection *conn;
616 if (!PURPLE_CONNECTION_IS_VALID(gc))
618 if (gsc) purple_ssl_close(gsc);
619 return NULL;
622 sip = gc->proto_data;
623 sip->fd = gsc->fd;
624 sip->gsc = gsc;
625 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
626 sip->connecting = FALSE;
627 sip->last_keepalive = time(NULL);
629 conn = connection_create(sip, gsc->fd);
631 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
633 return sip;
636 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
638 PurpleConnection *gc = data;
639 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
640 if (sip == NULL) return;
642 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
644 /* If there is more to write now */
645 if (sip->txbuf->bufused > 0) {
646 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
651 static void sendlater(PurpleConnection *gc, const char *buf)
653 struct sipe_account_data *sip = gc->proto_data;
655 if (!sip->connecting) {
656 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
657 if (sip->transport == SIPE_TRANSPORT_TLS){
658 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
659 } else {
660 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
661 purple_connection_error(gc, _("Couldn't create socket"));
664 sip->connecting = TRUE;
667 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
668 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
670 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
673 static void sendout_pkt(PurpleConnection *gc, const char *buf)
675 struct sipe_account_data *sip = gc->proto_data;
676 time_t currtime = time(NULL);
677 int writelen = strlen(buf);
679 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
680 if (sip->transport == SIPE_TRANSPORT_UDP) {
681 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
682 purple_debug_info("sipe", "could not send packet\n");
684 } else {
685 int ret;
686 if (sip->fd < 0) {
687 sendlater(gc, buf);
688 return;
691 if (sip->tx_handler) {
692 ret = -1;
693 errno = EAGAIN;
694 } else{
695 if (sip->gsc){
696 ret = purple_ssl_write(sip->gsc, buf, writelen);
697 }else{
698 ret = write(sip->fd, buf, writelen);
702 if (ret < 0 && errno == EAGAIN)
703 ret = 0;
704 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
705 sendlater(gc, buf);
706 return;
709 if (ret < writelen) {
710 if (!sip->tx_handler){
711 if (sip->gsc){
712 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
714 else{
715 sip->tx_handler = purple_input_add(sip->fd,
716 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
717 gc);
721 /* XXX: is it OK to do this? You might get part of a request sent
722 with part of another. */
723 if (sip->txbuf->bufused > 0)
724 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
726 purple_circ_buffer_append(sip->txbuf, buf + ret,
727 writelen - ret);
732 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
734 sendout_pkt(gc, buf);
735 return len;
738 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
740 GSList *tmp = msg->headers;
741 gchar *name;
742 gchar *value;
743 GString *outstr = g_string_new("");
744 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
745 while (tmp) {
746 name = ((struct siphdrelement*) (tmp->data))->name;
747 value = ((struct siphdrelement*) (tmp->data))->value;
748 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
749 tmp = g_slist_next(tmp);
751 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
752 sendout_pkt(sip->gc, outstr->str);
753 g_string_free(outstr, TRUE);
756 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
758 gchar * buf;
759 if (sip->registrar.ntlm_key) {
760 struct sipmsg_breakdown msgbd;
761 gchar *signature_input_str;
762 msgbd.msg = msg;
763 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
764 msgbd.rand = g_strdup_printf("%08x", g_random_int());
765 sip->registrar.ntlm_num++;
766 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
767 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
768 if (signature_input_str != NULL) {
769 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
770 msg->rand = g_strdup(msgbd.rand);
771 msg->num = g_strdup(msgbd.num);
772 g_free(signature_input_str);
774 sipmsg_breakdown_free(&msgbd);
777 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
778 buf = auth_header(sip, &sip->registrar, msg);
779 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
780 sipmsg_add_header(msg, "Authorization", buf);
781 } else {
782 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
784 g_free(buf);
785 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS")) {
786 sip->registrar.nc = 3;
787 sip->registrar.type = AUTH_TYPE_NTLM;
789 buf = auth_header(sip, &sip->registrar, msg);
790 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
791 g_free(buf);
792 } else {
793 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
797 static char *get_contact(struct sipe_account_data *sip)
799 return g_strdup(sip->contact);
803 * unused. Needed?
804 static char *get_contact_service(struct sipe_account_data *sip)
806 return g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, generateUUIDfromEPID(get_epid()));
807 //return g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
811 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
812 const char *text, const char *body)
814 gchar *name;
815 gchar *value;
816 GString *outstr = g_string_new("");
817 struct sipe_account_data *sip = gc->proto_data;
818 gchar *contact;
819 GSList *tmp;
821 sipmsg_remove_header(msg, "ms-user-data");
823 contact = get_contact(sip);
824 sipmsg_remove_header(msg, "Contact");
825 sipmsg_add_header(msg, "Contact", contact);
826 g_free(contact);
828 /* When sending the acknowlegements and errors, the content length from the original
829 message is still here, but there is no body; we need to make sure we're sending the
830 correct content length */
831 sipmsg_remove_header(msg, "Content-Length");
832 if (body) {
833 gchar len[12];
834 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
835 sipmsg_add_header(msg, "Content-Length", len);
836 } else {
837 sipmsg_remove_header(msg, "Content-Type");
838 sipmsg_add_header(msg, "Content-Length", "0");
841 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
842 //gchar * mic = "MICTODO";
843 msg->response = code;
845 sipmsg_remove_header(msg, "Authentication-Info");
846 sign_outgoing_message(msg, sip, msg->method);
848 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
849 tmp = msg->headers;
850 while (tmp) {
851 name = ((struct siphdrelement*) (tmp->data))->name;
852 value = ((struct siphdrelement*) (tmp->data))->value;
854 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
855 tmp = g_slist_next(tmp);
857 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
858 sendout_pkt(gc, outstr->str);
859 g_string_free(outstr, TRUE);
862 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
864 if (trans->msg) sipmsg_free(trans->msg);
865 sip->transactions = g_slist_remove(sip->transactions, trans);
866 g_free(trans);
869 static struct transaction *
870 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
872 struct transaction *trans = g_new0(struct transaction, 1);
873 trans->time = time(NULL);
874 trans->msg = (struct sipmsg *)msg;
875 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
876 trans->callback = callback;
877 sip->transactions = g_slist_append(sip->transactions, trans);
878 return trans;
881 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
883 struct transaction *trans;
884 GSList *transactions = sip->transactions;
885 gchar *cseq = sipmsg_find_header(msg, "CSeq");
887 while (transactions) {
888 trans = transactions->data;
889 if (!strcmp(trans->cseq, cseq)) {
890 return trans;
892 transactions = transactions->next;
895 return NULL;
898 static struct transaction *
899 send_sip_request(PurpleConnection *gc, const gchar *method,
900 const gchar *url, const gchar *to, const gchar *addheaders,
901 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
903 struct sipe_account_data *sip = gc->proto_data;
904 const char *addh = "";
905 char *buf;
906 struct sipmsg *msg;
907 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
908 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
909 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
910 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
911 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
912 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
913 gchar *route = strdup("");
914 gchar *epid = get_epid(); // TODO generate one per account/login
915 struct transaction *trans;
917 if (dialog && dialog->routes)
919 GSList *iter = dialog->routes;
921 while(iter)
923 char *tmp = route;
924 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
925 g_free(tmp);
926 iter = g_slist_next(iter);
930 if (!ourtag && !dialog) {
931 ourtag = gentag();
934 if (!strcmp(method, "REGISTER")) {
935 if (sip->regcallid) {
936 g_free(callid);
937 callid = g_strdup(sip->regcallid);
938 } else {
939 sip->regcallid = g_strdup(callid);
943 if (addheaders) addh = addheaders;
945 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
946 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
947 "From: <sip:%s>%s%s;epid=%s\r\n"
948 "To: <%s>%s%s%s%s\r\n"
949 "Max-Forwards: 70\r\n"
950 "CSeq: %d %s\r\n"
951 "User-Agent: %s\r\n"
952 "Call-ID: %s\r\n"
953 "%s%s"
954 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
955 method,
956 dialog && dialog->request ? dialog->request : url,
957 TRANSPORT_DESCRIPTOR,
958 purple_network_get_my_ip(-1),
959 sip->listenport,
960 branch ? ";branch=" : "",
961 branch ? branch : "",
962 sip->username,
963 ourtag ? ";tag=" : "",
964 ourtag ? ourtag : "",
965 epid,
967 theirtag ? ";tag=" : "",
968 theirtag ? theirtag : "",
969 theirepid ? ";epid=" : "",
970 theirepid ? theirepid : "",
971 dialog ? ++dialog->cseq : ++sip->cseq,
972 method,
973 useragent,
974 callid,
975 route,
976 addh,
977 body ? strlen(body) : 0,
978 body ? body : "");
981 //printf ("parsing msg buf:\n%s\n\n", buf);
982 msg = sipmsg_parse_msg(buf);
984 g_free(buf);
985 g_free(ourtag);
986 g_free(theirtag);
987 g_free(theirepid);
988 g_free(branch);
989 g_free(callid);
990 g_free(route);
991 g_free(epid);
993 sign_outgoing_message (msg, sip, method);
995 buf = sipmsg_to_string (msg);
997 /* add to ongoing transactions */
998 trans = transactions_add_buf(sip, msg, tc);
999 sendout_pkt(gc, buf);
1000 g_free(buf);
1002 return trans;
1005 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1007 gchar *from = g_strdup_printf("sip:%s", sip->username);
1008 gchar *contact = get_contact(sip);
1009 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1010 "Content-Type: application/SOAP+xml\r\n",contact);
1012 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1013 tr->payload = payload;
1015 g_free(from);
1016 g_free(contact);
1017 g_free(hdr);
1020 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1022 send_soap_request_with_cb(sip, body, NULL, NULL);
1025 static char *get_contact_register(struct sipe_account_data *sip)
1027 char *epid = get_epid();
1028 char *uuid = generateUUIDfromEPID(epid);
1029 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1030 g_free(uuid);
1031 g_free(epid);
1032 return(buf);
1035 static void do_register_exp(struct sipe_account_data *sip, int expire)
1037 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1038 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1039 char *to = g_strdup_printf("sip:%s", sip->username);
1040 char *contact = get_contact_register(sip);
1041 char *hdr = g_strdup_printf("Contact: %s\r\n"
1042 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1043 "Event: registration\r\n"
1044 "Allow-Events: presence\r\n"
1045 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1046 "%s", contact, expires);
1047 g_free(contact);
1048 g_free(expires);
1050 sip->registerstatus = 1;
1052 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1053 process_register_response);
1055 g_free(hdr);
1056 g_free(uri);
1057 g_free(to);
1060 static void do_register_cb(struct sipe_account_data *sip)
1062 do_register_exp(sip, -1);
1063 sip->reregister_set = FALSE;
1066 static void do_register(struct sipe_account_data *sip)
1068 do_register_exp(sip, -1);
1072 * Returns URI from provided To or From header.
1074 * Needs to g_free() after use.
1076 * @return URI with sip: prefix
1078 static gchar *parse_from(const gchar *hdr)
1080 gchar *from;
1081 const gchar *tmp, *tmp2 = hdr;
1083 if (!hdr) return NULL;
1084 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1085 tmp = strchr(hdr, '<');
1087 /* i hate the different SIP UA behaviours... */
1088 if (tmp) { /* sip address in <...> */
1089 tmp2 = tmp + 1;
1090 tmp = strchr(tmp2, '>');
1091 if (tmp) {
1092 from = g_strndup(tmp2, tmp - tmp2);
1093 } else {
1094 purple_debug_info("sipe", "found < without > in From\n");
1095 return NULL;
1097 } else {
1098 tmp = strchr(tmp2, ';');
1099 if (tmp) {
1100 from = g_strndup(tmp2, tmp - tmp2);
1101 } else {
1102 from = g_strdup(tmp2);
1105 purple_debug_info("sipe", "got %s\n", from);
1106 return from;
1109 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1111 va_list args;
1112 xmlnode * node = NULL;
1113 const gchar * name;
1115 va_start(args, parent);
1116 while ((name = va_arg(args, const char *)) != NULL) {
1117 node = xmlnode_get_child(parent, name);
1118 if (node == NULL) return NULL;
1119 parent = node;
1121 va_end(args);
1123 return node;
1127 static void
1128 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1130 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1131 send_soap_request(sip, body);
1132 g_free(body);
1135 static void
1136 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1138 if (allow) {
1139 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1140 } else {
1141 purple_debug_info("sipe", "Blocking contact %s\n", who);
1144 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1147 static
1148 void sipe_auth_user_cb(void * data)
1150 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1151 if (!job) return;
1153 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1154 g_free(job);
1157 static
1158 void sipe_deny_user_cb(void * data)
1160 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1161 if (!job) return;
1163 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1164 g_free(job);
1167 static void
1168 sipe_add_permit(PurpleConnection *gc, const char *name)
1170 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1171 sipe_contact_allow_deny(sip, name, TRUE);
1174 static void
1175 sipe_add_deny(PurpleConnection *gc, const char *name)
1177 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1178 sipe_contact_allow_deny(sip, name, FALSE);
1181 /*static void
1182 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1184 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1185 sipe_contact_set_acl(sip, name, "");
1188 static void
1189 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1191 xmlnode *watchers;
1192 xmlnode *watcher;
1193 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1194 if (msg->response != 0 && msg->response != 200) return;
1196 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1198 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1199 if (!watchers) return;
1201 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1202 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1203 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1204 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1206 // TODO pull out optional displayName to pass as alias
1207 if (remote_user) {
1208 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1209 job->who = remote_user;
1210 job->sip = sip;
1211 purple_account_request_authorization(
1212 sip->account,
1213 remote_user,
1214 NULL, // id
1215 alias,
1216 NULL, // message
1217 on_list,
1218 sipe_auth_user_cb,
1219 sipe_deny_user_cb,
1220 (void *) job);
1225 xmlnode_free(watchers);
1226 return;
1229 static void
1230 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1232 PurpleGroup * purple_group = purple_find_group(group->name);
1233 if (!purple_group) {
1234 purple_group = purple_group_new(group->name);
1235 purple_blist_add_group(purple_group, NULL);
1238 if (purple_group) {
1239 group->purple_group = purple_group;
1240 sip->groups = g_slist_append(sip->groups, group);
1241 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1242 } else {
1243 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1247 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1249 struct sipe_group *group;
1250 GSList *entry;
1251 if (sip == NULL) {
1252 return NULL;
1255 entry = sip->groups;
1256 while (entry) {
1257 group = entry->data;
1258 if (group->id == id) {
1259 return group;
1261 entry = entry->next;
1263 return NULL;
1266 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1268 struct sipe_group *group;
1269 GSList *entry;
1270 if (sip == NULL) {
1271 return NULL;
1274 entry = sip->groups;
1275 while (entry) {
1276 group = entry->data;
1277 if (!strcmp(group->name, name)) {
1278 return group;
1280 entry = entry->next;
1282 return NULL;
1285 static void
1286 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1288 gchar *body;
1289 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1290 body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1291 send_soap_request(sip, body);
1292 g_free(body);
1293 g_free(group->name);
1294 group->name = g_strdup(name);
1298 * Only appends if no such value already stored.
1299 * Like Set in Java.
1301 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1302 GSList * res = list;
1303 if (!g_slist_find_custom(list, data, func)) {
1304 res = g_slist_insert_sorted(list, data, func);
1306 return res;
1309 static int
1310 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1311 return group1->id - group2->id;
1315 * Returns string like "2 4 7 8" - group ids buddy belong to.
1317 static gchar *
1318 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1319 int i = 0;
1320 gchar *res;
1321 //creating array from GList, converting int to gchar*
1322 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1323 GSList *entry = buddy->groups;
1324 while (entry) {
1325 struct sipe_group * group = entry->data;
1326 ids_arr[i] = g_strdup_printf("%d", group->id);
1327 entry = entry->next;
1328 i++;
1330 ids_arr[i] = NULL;
1331 res = g_strjoinv(" ", ids_arr);
1332 g_strfreev(ids_arr);
1333 return res;
1337 * Sends buddy update to server
1339 static void
1340 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1342 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1343 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1345 if (buddy && purple_buddy) {
1346 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1347 gchar *body;
1348 gchar *groups = sipe_get_buddy_groups_string(buddy);
1349 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1351 body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1352 alias, groups, "true", buddy->name, sip->contacts_delta++
1354 send_soap_request(sip, body);
1355 g_free(groups);
1356 g_free(body);
1360 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1362 if (msg->response == 200) {
1363 struct sipe_group *group;
1364 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1365 xmlnode *xml;
1366 xmlnode *node;
1367 char *group_id;
1368 struct sipe_buddy *buddy;
1369 group->name = ctx->group_name;
1371 xml = xmlnode_from_str(msg->body, msg->bodylen);
1372 if (!xml) {
1373 g_free(ctx);
1374 return FALSE;
1377 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1378 if (!node) {
1379 g_free(ctx);
1380 xmlnode_free(xml);
1381 return FALSE;
1384 group_id = xmlnode_get_data(node);
1385 if (!group_id) {
1386 g_free(ctx);
1387 xmlnode_free(xml);
1388 return FALSE;
1391 group = g_new0(struct sipe_group, 1);
1392 group->id = (int)g_ascii_strtod(group_id, NULL);
1393 g_free(group_id);
1395 sipe_group_add(sip, group);
1397 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1398 if (buddy) {
1399 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1402 sipe_group_set_user(sip, ctx->user_name);
1404 g_free(ctx);
1405 xmlnode_free(xml);
1406 return TRUE;
1408 return FALSE;
1411 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1413 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1414 gchar *body;
1415 ctx->group_name = g_strdup(name);
1416 ctx->user_name = g_strdup(who);
1418 body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1419 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1420 g_free(body);
1424 * A timer callback
1425 * Should return FALSE if repetitive action is not needed
1427 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1429 gboolean ret;
1430 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1431 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1432 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1433 (sched_action->action)(sched_action->sip, sched_action->payload);
1434 ret = sched_action->repetitive;
1435 g_free(sched_action->payload);
1436 g_free(sched_action->name);
1437 g_free(sched_action);
1438 return ret;
1442 * Do schedule action for execution in the future.
1443 * Non repetitive execution.
1445 * @param name of action (will be copied)
1446 * @param timeout in seconds
1447 * @action callback function
1448 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1450 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1452 struct scheduled_action *sched_action;
1454 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1455 sched_action = g_new0(struct scheduled_action, 1);
1456 sched_action->repetitive = FALSE;
1457 sched_action->name = g_strdup(name);
1458 sched_action->action = action;
1459 sched_action->sip = sip;
1460 sched_action->payload = payload;
1461 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1462 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1463 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1467 * Kills action timer effectively cancelling
1468 * scheduled action
1470 * @param name of action
1472 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1474 GSList *entry;
1476 if (!sip->timeouts || !name) return;
1478 entry = sip->timeouts;
1479 while (entry) {
1480 struct scheduled_action *sched_action = entry->data;
1481 if(!strcmp(sched_action->name, name)) {
1482 GSList *to_delete = entry;
1483 entry = entry->next;
1484 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1485 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1486 purple_timeout_remove(sched_action->timeout_handler);
1487 g_free(sched_action->payload);
1488 g_free(sched_action->name);
1489 g_free(sched_action);
1490 } else {
1491 entry = entry->next;
1496 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1498 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1500 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1502 process_incoming_notify(sip, msg, FALSE, FALSE);
1504 return TRUE;
1507 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1509 gchar *tmp = *resources_uri;
1510 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1511 g_free(tmp);
1515 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1516 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1517 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1518 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1519 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1522 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip){
1523 gchar *to = g_strdup_printf("sip:%s", sip->username);
1524 gchar *contact = get_contact(sip);
1525 gchar *request;
1526 gchar *content;
1527 gchar *resources_uri = g_strdup("");
1528 gchar *require = "";
1529 gchar *accept = "";
1530 gchar *content_type;
1532 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1534 if (sip->msrtc_event_categories) {
1535 require = ", categoryList";
1536 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1537 content_type = "application/msrtc-adrl-categorylist+xml";
1538 content = g_strdup_printf(
1539 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1540 "<action name=\"subscribe\" id=\"63792024\">\n"
1541 "<adhocList>\n%s</adhocList>\n"
1542 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1543 "<category name=\"note\"/>\n"
1544 "<category name=\"state\"/>\n"
1545 "</categoryList>\n"
1546 "</action>\n"
1547 "</batchSub>", sip->username, resources_uri);
1548 } else {
1549 content_type = "application/adrl+xml";
1550 content = g_strdup_printf(
1551 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1552 "<create xmlns=\"\">\n%s</create>\n"
1553 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1555 g_free(resources_uri);
1557 request = g_strdup_printf(
1558 "Require: adhoclist%s\r\n"
1559 "Supported: eventlist\r\n"
1560 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1561 "Supported: ms-piggyback-first-notify\r\n"
1562 "Supported: com.microsoft.autoextend\r\n"
1563 "Supported: ms-benotify\r\n"
1564 "Proxy-Require: ms-benotify\r\n"
1565 "Event: presence\r\n"
1566 "Content-Type: %s\r\n"
1567 "Contact: %s\r\n", require, accept, content_type, contact);
1568 g_free(contact);
1570 /* subscribe to buddy presence */
1571 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1572 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1574 g_free(content);
1575 g_free(to);
1576 g_free(request);
1580 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1581 * The user sends a single SUBSCRIBE request to the subscribed contact.
1582 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1586 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1588 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1589 gchar *tmp = get_contact(sip);
1590 gchar *request;
1591 gchar *content;
1592 request = g_strdup_printf(
1593 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1594 "Supported: ms-piggyback-first-notify\r\n"
1595 "Supported: com.microsoft.autoextend\r\n"
1596 "Supported: ms-benotify\r\n"
1597 "Proxy-Require: ms-benotify\r\n"
1598 "Event: presence\r\n"
1599 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1600 "Contact: %s\r\n", tmp);
1602 content = g_strdup_printf(
1603 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1604 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1605 "<resource uri=\"%s\"/>\n"
1606 "</adhocList>\n"
1607 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1608 "<category name=\"note\"/>\n"
1609 "<category name=\"state\"/>\n"
1610 "</categoryList>\n"
1611 "</action>\n"
1612 "</batchSub>", sip->username, to
1615 g_free(tmp);
1617 /* subscribe to buddy presence */
1618 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1620 g_free(content);
1621 g_free(to);
1622 g_free(request);
1625 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1627 if (!purple_status_is_active(status))
1628 return;
1630 if (account->gc) {
1631 struct sipe_account_data *sip = account->gc->proto_data;
1633 if (sip) {
1634 g_free(sip->status);
1635 sip->status = g_strdup(purple_status_get_id(status));
1636 send_presence_info(sip);
1641 static void
1642 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1644 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1645 sipe_group_set_user(sip, name);
1648 static void
1649 sipe_group_buddy(PurpleConnection *gc,
1650 const char *who,
1651 const char *old_group_name,
1652 const char *new_group_name)
1654 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1655 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1656 struct sipe_group * old_group = NULL;
1657 struct sipe_group * new_group;
1659 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1660 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1662 if(!buddy) { // buddy not in roaming list
1663 return;
1666 if (old_group_name) {
1667 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1669 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1671 if (old_group) {
1672 buddy->groups = g_slist_remove(buddy->groups, old_group);
1673 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1676 if (!new_group) {
1677 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1678 } else {
1679 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1680 sipe_group_set_user(sip, who);
1684 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1686 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1687 struct sipe_buddy *b;
1689 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1691 // Prepend sip: if needed
1692 if (strncmp("sip:", buddy->name, 4)) {
1693 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1694 purple_blist_rename_buddy(buddy, buf);
1695 g_free(buf);
1698 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1699 b = g_new0(struct sipe_buddy, 1);
1700 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1701 b->name = g_strdup(buddy->name);
1702 g_hash_table_insert(sip->buddies, b->name, b);
1703 sipe_group_buddy(gc, b->name, NULL, group->name);
1704 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1705 } else {
1706 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1710 static void sipe_free_buddy(struct sipe_buddy *buddy)
1712 g_free(buddy->name);
1713 g_free(buddy->annotation);
1714 g_free(buddy->device_name);
1715 g_slist_free(buddy->groups);
1716 g_free(buddy);
1720 * Unassociates buddy from group first.
1721 * Then see if no groups left, removes buddy completely.
1722 * Otherwise updates buddy groups on server.
1724 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1726 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1727 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1728 struct sipe_group *g = NULL;
1730 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1732 if (!b) return;
1734 if (group) {
1735 g = sipe_group_find_by_name(sip, group->name);
1738 if (g) {
1739 b->groups = g_slist_remove(b->groups, g);
1740 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1743 if (g_slist_length(b->groups) < 1) {
1744 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1745 sipe_cancel_scheduled_action(sip, action_name);
1746 g_free(action_name);
1748 g_hash_table_remove(sip->buddies, buddy->name);
1750 if (b->name) {
1751 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1752 send_soap_request(sip, body);
1753 g_free(body);
1756 sipe_free_buddy(b);
1757 } else {
1758 //updates groups on server
1759 sipe_group_set_user(sip, b->name);
1764 static void
1765 sipe_rename_group(PurpleConnection *gc,
1766 const char *old_name,
1767 PurpleGroup *group,
1768 GList *moved_buddies)
1770 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1771 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1772 if (group) {
1773 sipe_group_rename(sip, s_group, group->name);
1774 } else {
1775 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1779 static void
1780 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1782 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1783 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1784 if (s_group) {
1785 gchar *body;
1786 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1787 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1788 send_soap_request(sip, body);
1789 g_free(body);
1791 sip->groups = g_slist_remove(sip->groups, s_group);
1792 g_free(s_group->name);
1793 } else {
1794 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1798 static GList *sipe_status_types(PurpleAccount *acc)
1800 PurpleStatusType *type;
1801 GList *types = NULL;
1803 // Online
1804 type = purple_status_type_new_with_attrs(
1805 PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE,
1806 // Translators: noun
1807 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1808 NULL);
1809 types = g_list_append(types, type);
1811 // Busy
1812 type = purple_status_type_new_with_attrs(
1813 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1814 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1815 NULL);
1816 types = g_list_append(types, type);
1818 // Do Not Disturb (Not let user set it)
1819 type = purple_status_type_new_with_attrs(
1820 PURPLE_STATUS_UNAVAILABLE, "do-not-disturb", "Do Not Disturb", TRUE, FALSE, FALSE,
1821 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1822 NULL);
1823 types = g_list_append(types, type);
1825 // Be Right Back
1826 type = purple_status_type_new_with_attrs(
1827 PURPLE_STATUS_AWAY, "be-right-back", _("Be Right Back"), TRUE, TRUE, FALSE,
1828 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1829 NULL);
1830 types = g_list_append(types, type);
1832 // Away
1833 type = purple_status_type_new_with_attrs(
1834 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1835 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1836 NULL);
1837 types = g_list_append(types, type);
1839 //On The Phone
1840 type = purple_status_type_new_with_attrs(
1841 PURPLE_STATUS_UNAVAILABLE, "on-the-phone", _("On The Phone"), TRUE, TRUE, FALSE,
1842 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1843 NULL);
1844 types = g_list_append(types, type);
1846 //Out To Lunch
1847 type = purple_status_type_new_with_attrs(
1848 PURPLE_STATUS_AWAY, "out-to-lunch", "Out To Lunch", TRUE, TRUE, FALSE,
1849 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1850 NULL);
1851 types = g_list_append(types, type);
1853 //Appear Offline
1854 type = purple_status_type_new_full(
1855 PURPLE_STATUS_INVISIBLE, NULL, "Appear Offline", TRUE, TRUE, FALSE);
1856 types = g_list_append(types, type);
1858 // Offline
1859 type = purple_status_type_new_full(
1860 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1861 types = g_list_append(types, type);
1863 return types;
1867 * A callback for g_hash_table_foreach
1869 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1871 sipe_subscribe_presence_single(sip, buddy->name);
1875 * Removes entries from purple buddy list
1876 * that does not correspond ones in the roaming contact list.
1878 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1879 GSList *buddies = purple_find_buddies(sip->account, NULL);
1880 GSList *entry = buddies;
1881 struct sipe_buddy *buddy;
1882 PurpleBuddy *b;
1883 PurpleGroup *g;
1885 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1886 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1887 while (entry) {
1888 b = entry->data;
1889 g = purple_buddy_get_group(b);
1890 buddy = g_hash_table_lookup(sip->buddies, b->name);
1891 if(buddy) {
1892 gboolean in_sipe_groups = FALSE;
1893 GSList *entry2 = buddy->groups;
1894 while (entry2) {
1895 struct sipe_group *group = entry2->data;
1896 if (!strcmp(group->name, g->name)) {
1897 in_sipe_groups = TRUE;
1898 break;
1900 entry2 = entry2->next;
1902 if(!in_sipe_groups) {
1903 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1904 purple_blist_remove_buddy(b);
1906 } else {
1907 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1908 purple_blist_remove_buddy(b);
1910 entry = entry->next;
1914 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1916 int len = msg->bodylen;
1918 gchar *tmp = sipmsg_find_header(msg, "Event");
1919 xmlnode *item;
1920 xmlnode *isc;
1921 const gchar *contacts_delta;
1922 xmlnode *group_node;
1923 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1924 return FALSE;
1927 /* Convert the contact from XML to Purple Buddies */
1928 isc = xmlnode_from_str(msg->body, len);
1929 if (!isc) {
1930 return FALSE;
1933 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1934 if (contacts_delta) {
1935 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1938 /* Parse groups */
1939 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1940 struct sipe_group * group = g_new0(struct sipe_group, 1);
1941 const char *name = xmlnode_get_attrib(group_node, "name");
1943 if (!strncmp(name, "~", 1)) {
1944 // TODO translate
1945 name = "Other Contacts";
1947 group->name = g_strdup(name);
1948 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1950 sipe_group_add(sip, group);
1953 // Make sure we have at least one group
1954 if (g_slist_length(sip->groups) == 0) {
1955 struct sipe_group * group = g_new0(struct sipe_group, 1);
1956 PurpleGroup *purple_group;
1957 // TODO translate
1958 group->name = g_strdup("Other Contacts");
1959 group->id = 1;
1960 purple_group = purple_group_new(group->name);
1961 purple_blist_add_group(purple_group, NULL);
1962 sip->groups = g_slist_append(sip->groups, group);
1965 /* Parse contacts */
1966 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1967 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1968 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1969 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1970 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1971 gchar **item_groups;
1972 struct sipe_group *group = NULL;
1973 struct sipe_buddy *buddy = NULL;
1974 int i = 0;
1976 // assign to group Other Contacts if nothing else received
1977 if(!groups || !strcmp("", groups) ) {
1978 group = sipe_group_find_by_name(sip, "Other Contacts");
1979 groups = group ? g_strdup_printf("%d", group->id) : "1";
1982 item_groups = g_strsplit(groups, " ", 0);
1984 while (item_groups[i]) {
1985 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1987 // If couldn't find the right group for this contact, just put them in the first group we have
1988 if (group == NULL && g_slist_length(sip->groups) > 0) {
1989 group = sip->groups->data;
1992 if (group != NULL) {
1993 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1994 if (!b){
1995 b = purple_buddy_new(sip->account, buddy_name, uri);
1996 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1999 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2000 if (name != NULL && strlen(name) != 0) {
2001 purple_blist_alias_buddy(b, name);
2005 if (!buddy) {
2006 buddy = g_new0(struct sipe_buddy, 1);
2007 buddy->name = g_strdup(b->name);
2008 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2011 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2013 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2014 } else {
2015 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2016 name);
2019 i++;
2020 } // while, contact groups
2021 g_strfreev(item_groups);
2022 g_free(groups);
2023 g_free(name);
2024 g_free(buddy_name);
2025 g_free(uri);
2027 } // for, contacts
2029 xmlnode_free(isc);
2031 sipe_cleanup_local_blist(sip);
2033 //subscribe to buddies
2034 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2035 //if(sip->msrtc_event_categories){
2036 sipe_subscribe_presence_batched(sip);
2037 //}else{
2038 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2040 sip->subscribed_buddies = TRUE;
2043 return 0;
2047 * Subscribe roaming contacts
2049 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2051 gchar *to = g_strdup_printf("sip:%s", sip->username);
2052 gchar *tmp = get_contact(sip);
2053 gchar *hdr = g_strdup_printf(
2054 "Event: vnd-microsoft-roaming-contacts\r\n"
2055 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2056 "Supported: com.microsoft.autoextend\r\n"
2057 "Supported: ms-benotify\r\n"
2058 "Proxy-Require: ms-benotify\r\n"
2059 "Supported: ms-piggyback-first-notify\r\n"
2060 "Contact: %s\r\n", tmp);
2061 g_free(tmp);
2063 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2064 g_free(to);
2065 g_free(hdr);
2068 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2070 gchar *to = g_strdup_printf("sip:%s", sip->username);
2071 gchar *tmp = get_contact(sip);
2072 gchar *hdr = g_strdup_printf(
2073 "Event: presence.wpending\r\n"
2074 "Accept: text/xml+msrtc.wpending\r\n"
2075 "Supported: com.microsoft.autoextend\r\n"
2076 "Supported: ms-benotify\r\n"
2077 "Proxy-Require: ms-benotify\r\n"
2078 "Supported: ms-piggyback-first-notify\r\n"
2079 "Contact: %s\r\n", tmp);
2080 g_free(tmp);
2082 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2083 g_free(to);
2084 g_free(hdr);
2087 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2089 const gchar *contacts_delta;
2090 xmlnode *xml;
2092 xml = xmlnode_from_str(msg->body, msg->bodylen);
2093 if (!xml)
2095 return;
2098 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2099 if (contacts_delta)
2101 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2104 xmlnode_free(xml);
2108 * When we receive some self (BE) NOTIFY with a new subscriber
2109 * we sends a setSubscribers request to him [SIP-PRES]
2113 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2115 gchar *contact;
2116 gchar *to;
2117 xmlnode *xml;
2118 xmlnode *node;
2120 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2122 xml = xmlnode_from_str(msg->body, msg->bodylen);
2123 if (!xml) return;
2125 contact = get_contact(sip);
2126 to = g_strdup_printf("sip:%s", sip->username);
2128 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2129 const char *user;
2130 gchar *hdr;
2131 gchar *body;
2133 user = xmlnode_get_attrib(node, "user");
2134 if (!user) continue;
2136 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2138 hdr = g_strdup_printf(
2139 "Contact: %s\r\n"
2140 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2142 body = g_strdup_printf(
2143 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2144 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2145 "</setSubscribers>", user);
2147 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2148 g_free(body);
2149 g_free(hdr);
2152 g_free(to);
2153 g_free(contact);
2154 xmlnode_free(xml);
2157 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2159 gchar *to = g_strdup_printf("sip:%s", sip->username);
2160 gchar *tmp = get_contact(sip);
2161 gchar *hdr = g_strdup_printf(
2162 "Event: vnd-microsoft-roaming-ACL\r\n"
2163 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2164 "Supported: com.microsoft.autoextend\r\n"
2165 "Supported: ms-benotify\r\n"
2166 "Proxy-Require: ms-benotify\r\n"
2167 "Supported: ms-piggyback-first-notify\r\n"
2168 "Contact: %s\r\n", tmp);
2169 g_free(tmp);
2171 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2172 g_free(to);
2173 g_free(hdr);
2177 * To request for presence information about the user, access level settings that have already been configured by the user
2178 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2179 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2182 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2184 gchar *to = g_strdup_printf("sip:%s", sip->username);
2185 gchar *tmp = get_contact(sip);
2186 gchar *hdr = g_strdup_printf(
2187 "Event: vnd-microsoft-roaming-self\r\n"
2188 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2189 "Supported: com.microsoft.autoextend\r\n"
2190 "Supported: ms-benotify\r\n"
2191 "Proxy-Require: ms-benotify\r\n"
2192 "Supported: ms-piggyback-first-notify\r\n"
2193 "Contact: %s\r\n"
2194 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2196 gchar *body=g_strdup(
2197 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2198 "<roaming type=\"categories\"/>"
2199 "<roaming type=\"containers\"/>"
2200 "<roaming type=\"subscribers\"/></roamingList>");
2202 g_free(tmp);
2203 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2204 g_free(body);
2205 g_free(to);
2206 g_free(hdr);
2209 /** Subscription for provisioning information to help with initial
2210 * configuration. This subscription is a one-time query (denoted by the Expires header,
2211 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2212 * configuration, meeting policies, and policy settings that Communicator must enforce.
2213 * TODO: for what we need this information.
2216 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2218 gchar *to = g_strdup_printf("sip:%s", sip->username);
2219 gchar *tmp = get_contact(sip);
2220 gchar *hdr = g_strdup_printf(
2221 "Event: vnd-microsoft-provisioning-v2\r\n"
2222 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2223 "Supported: com.microsoft.autoextend\r\n"
2224 "Supported: ms-benotify\r\n"
2225 "Proxy-Require: ms-benotify\r\n"
2226 "Supported: ms-piggyback-first-notify\r\n"
2227 "Expires: 0\r\n"
2228 "Contact: %s\r\n"
2229 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2230 gchar *body = g_strdup(
2231 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2232 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2233 "<provisioningGroup name=\"ucPolicy\"/>"
2234 "</provisioningGroupList>");
2236 g_free(tmp);
2237 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2238 g_free(body);
2239 g_free(to);
2240 g_free(hdr);
2243 /* IM Session (INVITE and MESSAGE methods) */
2245 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2247 struct sip_im_session *session;
2248 GSList *entry;
2249 if (sip == NULL || who == NULL) {
2250 return NULL;
2253 entry = sip->im_sessions;
2254 while (entry) {
2255 session = entry->data;
2256 if ((who != NULL && !strcmp(who, session->with))) {
2257 return session;
2259 entry = entry->next;
2261 return NULL;
2264 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2266 struct sip_im_session *session = find_im_session(sip, who);
2267 if (!session) {
2268 session = g_new0(struct sip_im_session, 1);
2269 session->with = g_strdup(who);
2270 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2272 return session;
2275 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2277 struct sip_dialog *dialog = session->dialog;
2278 GSList *entry;
2280 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2282 if (dialog) {
2283 entry = dialog->routes;
2284 while (entry) {
2285 g_free(entry->data);
2286 entry = g_slist_remove(entry, entry->data);
2288 entry = dialog->supported;
2289 while (entry) {
2290 g_free(entry->data);
2291 entry = g_slist_remove(entry, entry->data);
2293 g_free(dialog->callid);
2294 g_free(dialog->ourtag);
2295 g_free(dialog->theirtag);
2296 g_free(dialog->theirepid);
2297 g_free(dialog->request);
2299 g_free(session->dialog);
2301 entry = session->outgoing_message_queue;
2302 while (entry) {
2303 g_free(entry->data);
2304 entry = g_slist_remove(entry, entry->data);
2307 g_free(session->with);
2308 g_free(session);
2311 static gboolean
2312 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2314 gboolean ret = TRUE;
2316 if (msg->response != 200) {
2317 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2318 return FALSE;
2321 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2323 return ret;
2327 * Asks UA/proxy about its capabilities.
2329 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2331 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2332 gchar *contact = get_contact(sip);
2333 gchar *request;
2334 request = g_strdup_printf(
2335 "Accept: application/sdp\r\n"
2336 "Contact: %s\r\n", contact);
2338 g_free(contact);
2340 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2342 g_free(to);
2343 g_free(request);
2346 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2348 char *msg, *msg_tmp;
2349 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2350 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2351 g_free(msg_tmp);
2352 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2353 "possibly because one or more persons are offline:\n%s") ,
2354 msg ? msg : "");
2355 purple_conv_present_error(with, sip->account, msg_tmp);
2356 g_free(msg);
2357 g_free(msg_tmp);
2360 static void sipe_im_remove_first_from_queue (struct sip_im_session * session);
2361 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2363 static gboolean
2364 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2366 gboolean ret = TRUE;
2367 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2368 struct sip_im_session * session = find_im_session(sip, with);
2369 struct sip_dialog *dialog;
2371 if (!session) {
2372 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2373 g_free(with);
2374 return FALSE;
2377 if (msg->response != 200) {
2378 gchar *queued_msg = NULL;
2379 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2381 if (session->outgoing_message_queue) {
2382 queued_msg = session->outgoing_message_queue->data;
2384 sipe_present_message_undelivered_err(with, sip, queued_msg);
2385 im_session_destroy(sip, session);
2386 g_free(with);
2387 return FALSE;
2390 dialog = session->dialog;
2391 if (!dialog) {
2392 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2393 ret = FALSE;
2396 sipe_im_remove_first_from_queue(session);
2397 sipe_im_process_queue(sip, session);
2398 g_free(with);
2399 return ret;
2402 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2404 gchar *hdr;
2405 gchar *fullto;
2406 gchar *tmp;
2407 char *msgformat;
2408 char *msgtext;
2409 gchar *msgr_value;
2410 gchar *msgr;
2412 if (strncmp("sip:", session->with, 4)) {
2413 fullto = g_strdup_printf("sip:%s", session->with);
2414 } else {
2415 fullto = g_strdup(session->with);
2418 sipe_parse_html(msg, &msgformat, &msgtext);
2419 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2421 msgr_value = sipmsg_get_msgr_string(msgformat);
2422 g_free(msgformat);
2423 if (msgr_value) {
2424 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2425 g_free(msgr_value);
2426 } else {
2427 msgr = g_strdup("");
2430 tmp = get_contact(sip);
2431 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2432 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2433 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2434 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2435 tmp, msgr);
2436 g_free(tmp);
2437 g_free(msgr);
2439 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2440 g_free(msgtext);
2441 g_free(hdr);
2442 g_free(fullto);
2446 static void
2447 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2449 GSList *entry = session->outgoing_message_queue;
2450 if (entry) {
2451 char *queued_msg = entry->data;
2452 sipe_send_message(sip, session, queued_msg);
2456 static void
2457 sipe_im_remove_first_from_queue (struct sip_im_session * session)
2459 if (session && session->outgoing_message_queue) {
2460 char *queued_msg = session->outgoing_message_queue->data;
2461 // Remove from the queue and free the string
2462 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2463 g_free(queued_msg);
2467 static void
2468 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2470 GSList *hdr = msg->headers;
2471 struct siphdrelement *elem;
2472 gchar *contact;
2474 while(hdr)
2476 elem = hdr->data;
2477 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2479 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2480 dialog->routes = g_slist_append(dialog->routes, route);
2482 hdr = g_slist_next(hdr);
2485 if (outgoing)
2487 dialog->routes = g_slist_reverse(dialog->routes);
2490 if (dialog->routes)
2492 dialog->request = dialog->routes->data;
2493 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2496 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2497 dialog->routes = g_slist_append(dialog->routes, contact);
2500 static void
2501 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2503 GSList *hdr = msg->headers;
2504 struct siphdrelement *elem;
2505 while(hdr)
2507 elem = hdr->data;
2508 if(!g_ascii_strcasecmp(elem->name, "Supported")
2509 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2511 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2514 hdr = g_slist_next(hdr);
2518 static void
2519 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2521 gchar *us = outgoing ? "From" : "To";
2522 gchar *them = outgoing ? "To" : "From";
2524 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2525 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2526 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2527 if (!dialog->theirepid) {
2528 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2530 if (!dialog->theirepid) {
2531 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2534 sipe_get_route_header(msg, dialog, outgoing);
2535 sipe_get_supported_header(msg, dialog, outgoing);
2539 static gboolean
2540 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2542 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2543 struct sip_im_session * session = find_im_session(sip, with);
2544 struct sip_dialog *dialog;
2546 if (!session) {
2547 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2548 g_free(with);
2549 return FALSE;
2552 if (msg->response != 200) {
2553 gchar *queued_msg = NULL;
2554 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2556 if (session->outgoing_message_queue) {
2557 queued_msg = session->outgoing_message_queue->data;
2559 sipe_present_message_undelivered_err(with, sip, queued_msg);
2561 im_session_destroy(sip, session);
2562 g_free(with);
2563 return FALSE;
2566 dialog = session->dialog;
2567 if (!dialog) {
2568 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2569 g_free(with);
2570 return FALSE;
2573 sipe_parse_dialog(msg, dialog, TRUE);
2574 dialog->cseq = 0;
2576 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2577 session->outgoing_invite = NULL;
2578 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2579 sipe_im_remove_first_from_queue(session);
2580 } else {
2581 sipe_im_process_queue(sip, session);
2584 g_free(with);
2585 return TRUE;
2589 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
2591 gchar *hdr;
2592 gchar *to;
2593 gchar *contact;
2594 gchar *body;
2595 char *msgformat;
2596 char *msgtext;
2597 char *base64_msg;
2598 char *ms_text_format;
2599 gchar *msgr_value;
2600 gchar *msgr;
2602 if (session->dialog) {
2603 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2604 return;
2607 session->dialog = g_new0(struct sip_dialog, 1);
2609 if (strstr(session->with, "sip:")) {
2610 to = g_strdup(session->with);
2611 } else {
2612 to = g_strdup_printf("sip:%s", session->with);
2615 sipe_parse_html(msg_body, &msgformat, &msgtext);
2616 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2618 msgr_value = sipmsg_get_msgr_string(msgformat);
2619 g_free(msgformat);
2620 msgr = "";
2621 if (msgr_value) {
2622 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2623 g_free(msgr_value);
2626 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2627 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2628 g_free(msgtext);
2629 g_free(msgr);
2630 g_free(base64_msg);
2632 contact = get_contact(sip);
2633 hdr = g_strdup_printf(
2634 "Contact: %s\r\n%s"
2635 "Content-Type: application/sdp\r\n",
2636 contact, ms_text_format);
2637 g_free(ms_text_format);
2639 body = g_strdup_printf(
2640 "v=0\r\n"
2641 "o=- 0 0 IN IP4 %s\r\n"
2642 "s=session\r\n"
2643 "c=IN IP4 %s\r\n"
2644 "t=0 0\r\n"
2645 "m=message %d sip null\r\n"
2646 "a=accept-types:text/plain text/html image/gif "
2647 "multipart/alternative application/im-iscomposing+xml\r\n",
2648 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2650 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2651 to, to, hdr, body, session->dialog, process_invite_response);
2653 g_free(to);
2654 g_free(body);
2655 g_free(hdr);
2656 g_free(contact);
2659 static void
2660 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2662 if (session) {
2663 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2664 im_session_destroy(sip, session);
2668 static void
2669 sipe_convo_closed(PurpleConnection * gc, const char *who)
2671 struct sipe_account_data *sip = gc->proto_data;
2673 purple_debug_info("sipe", "conversation with %s closed\n", who);
2674 im_session_close(sip, find_im_session(sip, who));
2677 static void
2678 im_session_close_all (struct sipe_account_data *sip)
2680 GSList *entry = sip->im_sessions;
2681 while (entry) {
2682 im_session_close (sip, entry->data);
2683 entry = sip->im_sessions;
2687 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2689 struct sipe_account_data *sip;
2690 gchar *to;
2691 gchar *text;
2692 struct sip_im_session *session;
2694 sip = gc->proto_data;
2695 to = g_strdup(who);
2696 text = g_strdup(what);
2698 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2700 session = find_or_create_im_session(sip, who);
2702 // Queue the message
2703 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
2705 if (session->dialog && session->dialog->callid) {
2706 sipe_im_process_queue(sip, session);
2707 } else if (!session->outgoing_invite) {
2708 // Need to send the INVITE to get the outgoing dialog setup
2709 sipe_invite(sip, session, text);
2712 g_free(to);
2713 g_free(text);
2714 return 1;
2718 /* End IM Session (INVITE and MESSAGE methods) */
2720 static unsigned int
2721 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2723 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2724 struct sip_im_session *session;
2726 if (state == PURPLE_NOT_TYPING)
2727 return 0;
2729 session = find_im_session(sip, who);
2731 if (session && session->dialog) {
2732 send_sip_request(gc, "INFO", who, who,
2733 "Content-Type: application/xml\r\n",
2734 SIPE_SEND_TYPING, session->dialog, NULL);
2737 return SIPE_TYPING_SEND_TIMEOUT;
2740 static gboolean resend_timeout(struct sipe_account_data *sip)
2742 GSList *tmp = sip->transactions;
2743 time_t currtime = time(NULL);
2744 while (tmp) {
2745 struct transaction *trans = tmp->data;
2746 tmp = tmp->next;
2747 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2748 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2749 /* TODO 408 */
2750 } else {
2751 if ((currtime - trans->time > 2) && trans->retries == 0) {
2752 trans->retries++;
2753 sendout_sipmsg(sip, trans->msg);
2757 return TRUE;
2760 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2762 /* register again when security token expires */
2763 /* we have to start a new authentication as the security token
2764 * is almost expired by sending a not signed REGISTER message */
2765 purple_debug_info("sipe", "do a full reauthentication\n");
2766 sipe_auth_free(&sip->registrar);
2767 sip->registerstatus = 0;
2768 do_register(sip);
2769 sip->reauthenticate_set = FALSE;
2772 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2774 gchar *from;
2775 gchar *contenttype;
2776 gboolean found = FALSE;
2778 from = parse_from(sipmsg_find_header(msg, "From"));
2780 if (!from) return;
2782 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2784 contenttype = sipmsg_find_header(msg, "Content-Type");
2785 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2786 gchar *msgr = sipmsg_find_part_of_header(contenttype, "msgr=", NULL, NULL);
2787 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2789 gchar *body_esc = g_markup_escape_text(msg->body, -1);
2790 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2791 g_free(msgr);
2792 g_free(body_esc);
2793 g_free(x_mms_im_format);
2795 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2796 g_free(body_html);
2797 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2798 found = TRUE;
2799 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2800 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2801 xmlnode *state;
2802 gchar *statedata;
2804 if (!isc) {
2805 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2806 return;
2809 state = xmlnode_get_child(isc, "state");
2811 if (!state) {
2812 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2813 xmlnode_free(isc);
2814 return;
2817 statedata = xmlnode_get_data(state);
2818 if (statedata) {
2819 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2820 else serv_got_typing_stopped(sip->gc, from);
2822 g_free(statedata);
2824 xmlnode_free(isc);
2825 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2826 found = TRUE;
2828 if (!found) {
2829 purple_debug_info("sipe", "got unknown mime-type");
2830 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2832 g_free(from);
2835 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2837 gchar *ms_text_format;
2838 gchar *from;
2839 gchar *body;
2840 struct sip_im_session *session;
2842 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
2844 // Only accept text invitations
2845 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2846 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2847 return;
2850 from = parse_from(sipmsg_find_header(msg, "From"));
2851 session = find_or_create_im_session (sip, from);
2852 if (session) {
2853 if (session->dialog) {
2854 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2855 } else {
2856 session->dialog = g_new0(struct sip_dialog, 1);
2858 sipe_parse_dialog(msg, session->dialog, FALSE);
2860 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2861 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2862 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2863 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2865 } else {
2866 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2869 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2870 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2871 if (ms_text_format && !strncmp(ms_text_format, "text/plain", 10)) {
2872 gchar *msgr = sipmsg_find_part_of_header(ms_text_format, "msgr=", ";", NULL);
2873 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2875 gchar *ms_body = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
2876 g_free(msgr);
2877 if (ms_body) {
2878 gchar *body = purple_base64_decode(ms_body, NULL);
2879 gchar *body_esc = g_markup_escape_text(body, -1);
2880 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2881 g_free(ms_body);
2882 g_free(body_esc);
2883 g_free(body);
2884 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2885 g_free(body_html);
2886 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2888 g_free(x_mms_im_format);
2890 g_free(from);
2892 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2893 sipmsg_remove_header(msg, "Ms-Text-Format");
2894 sipmsg_remove_header(msg, "EndPoints");
2895 sipmsg_remove_header(msg, "User-Agent");
2896 sipmsg_remove_header(msg, "Roster-Manager");
2898 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2899 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2901 body = g_strdup_printf(
2902 "v=0\r\n"
2903 "o=- 0 0 IN IP4 %s\r\n"
2904 "s=session\r\n"
2905 "c=IN IP4 %s\r\n"
2906 "t=0 0\r\n"
2907 "m=message %d sip sip:%s\r\n"
2908 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2909 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2910 sip->realport, sip->username);
2911 send_sip_response(sip->gc, msg, 200, "OK", body);
2912 g_free(body);
2915 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
2917 gchar *from;
2918 gchar *body;
2919 struct sip_im_session *session;
2922 from = parse_from(sipmsg_find_header(msg, "From"));
2923 session = find_or_create_im_session (sip, from);
2924 if (session) {
2925 if (session->dialog) {
2926 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2927 } else {
2928 session->dialog = g_new0(struct sip_dialog, 1);
2930 sipe_parse_dialog(msg, session->dialog, FALSE);
2932 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2933 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2934 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2935 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2937 } else {
2938 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2941 g_free(from);
2943 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2944 sipmsg_remove_header(msg, "EndPoints");
2945 sipmsg_remove_header(msg, "User-Agent");
2947 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
2948 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2950 body = g_strdup_printf(
2951 "v=0\r\n"
2952 "o=- 0 0 IN IP4 0.0.0.0\r\n"
2953 "s=session\r\n"
2954 "c=IN IP4 0.0.0.0\r\n"
2955 "t=0 0\r\n"
2956 "m=message %d sip sip:%s\r\n"
2957 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2958 sip->realport, sip->username);
2959 send_sip_response(sip->gc, msg, 200, "OK", body);
2960 g_free(body);
2963 static void sipe_connection_cleanup(struct sipe_account_data *);
2964 static void create_connection(struct sipe_account_data *, gchar *, int);
2966 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2968 gchar *tmp;
2969 //gchar krb5_token;
2970 const gchar *expires_header;
2971 int expires;
2972 GSList *hdr = msg->headers;
2973 struct siphdrelement *elem;
2975 expires_header = sipmsg_find_header(msg, "Expires");
2976 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2977 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2979 switch (msg->response) {
2980 case 200:
2981 if (expires == 0) {
2982 sip->registerstatus = 0;
2983 } else {
2984 int i = 0;
2985 gchar *contact_hdr = NULL;
2986 gchar *gruu = NULL;
2987 gchar *epid;
2988 gchar *uuid;
2990 if (!sip->reregister_set) {
2991 gchar *action_name = g_strdup_printf("<%s>", "registration");
2992 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
2993 g_free(action_name);
2994 sip->reregister_set = TRUE;
2997 sip->registerstatus = 3;
2999 if (!sip->reauthenticate_set) {
3000 /* we have to reauthenticate as our security token expires
3001 after eight hours (be five minutes early) */
3002 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3003 guint reauth_timeout = (8 * 3600) - 360;
3004 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3005 g_free(action_name);
3006 sip->reauthenticate_set = TRUE;
3009 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3011 epid = get_epid();
3012 uuid = generateUUIDfromEPID(epid);
3013 g_free(epid);
3015 // There can be multiple Contact headers (one per location where the user is logged in) so
3016 // make sure to only get the one for this uuid
3017 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3018 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3019 if (valid_contact) {
3020 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3021 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3022 g_free(valid_contact);
3023 break;
3024 } else {
3025 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3028 g_free(uuid);
3030 g_free(sip->contact);
3031 if(gruu) {
3032 sip->contact = g_strdup_printf("<%s>", gruu);
3033 g_free(gruu);
3034 } else {
3035 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3036 sip->contact = g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
3038 sip->msrtc_event_categories = FALSE;
3040 while(hdr)
3042 elem = hdr->data;
3043 if(!g_ascii_strcasecmp(elem->name, "Supported"))
3045 if (strstr(elem->value, "msrtc-event-categories")){
3046 sip->msrtc_event_categories = TRUE;
3048 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s, %d\n", elem->value, sip->msrtc_event_categories);
3050 hdr = g_slist_next(hdr);
3053 if (!sip->subscribed) { //do it just once, not every re-register
3054 tmp = sipmsg_find_header(msg, "Allow-Events");
3055 sipe_options_request(sip, sip->sipdomain);
3056 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
3057 sipe_subscribe_roaming_contacts(sip, msg);
3059 sipe_subscribe_roaming_acl(sip, msg);
3060 sipe_subscribe_roaming_self(sip, msg);
3061 sipe_subscribe_roaming_provisioning(sip, msg);
3062 sipe_subscribe_presence_wpending(sip, msg);
3063 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3064 sip->subscribed = TRUE;
3067 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
3068 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
3069 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
3070 } else {
3071 tmp = sipmsg_find_header(msg, "ms-keep-alive");
3072 if (tmp) {
3073 sipe_keep_alive_timeout(sip, tmp);
3077 // Should we remove the transaction here?
3078 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3079 transactions_remove(sip, tc);
3081 break;
3082 case 301:
3084 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3086 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3087 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3088 gchar **tmp;
3089 gchar *hostname;
3090 int port = 0;
3091 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3092 int i = 1;
3094 tmp = g_strsplit(parts[0], ":", 0);
3095 hostname = g_strdup(tmp[0]);
3096 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3097 g_strfreev(tmp);
3099 while (parts[i]) {
3100 tmp = g_strsplit(parts[i], "=", 0);
3101 if (tmp[1]) {
3102 if (g_strcasecmp("transport", tmp[0]) == 0) {
3103 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3104 transport = SIPE_TRANSPORT_TCP;
3105 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3106 transport = SIPE_TRANSPORT_UDP;
3110 g_strfreev(tmp);
3111 i++;
3113 g_strfreev(parts);
3115 /* Close old connection */
3116 sipe_connection_cleanup(sip);
3118 /* Create new connection */
3119 sip->transport = transport;
3120 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3121 hostname, port, TRANSPORT_DESCRIPTOR);
3122 create_connection(sip, hostname, port);
3124 g_free(redirect);
3126 break;
3127 case 401:
3128 if (sip->registerstatus != 2) {
3129 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3130 if (sip->registrar.retries > 3) {
3131 sip->gc->wants_to_die = TRUE;
3132 purple_connection_error(sip->gc, _("Wrong Password"));
3133 return TRUE;
3135 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3136 tmp = sipmsg_find_auth_header(msg, "NTLM");
3137 } else {
3138 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3140 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3141 fill_auth(sip, tmp, &sip->registrar);
3142 sip->registerstatus = 2;
3143 if (sip->account->disconnecting) {
3144 do_register_exp(sip, 0);
3145 } else {
3146 do_register(sip);
3149 break;
3150 case 403:
3152 const gchar *warning = sipmsg_find_header(msg, "Warning");
3153 if (warning != NULL) {
3154 /* Example header:
3155 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3157 gchar **tmp = g_strsplit(warning, "\"", 0);
3158 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3159 g_strfreev(tmp);
3160 } else {
3161 warning = _("You have been rejected by the server");
3164 sip->gc->wants_to_die = TRUE;
3165 purple_connection_error(sip->gc, warning);
3166 return TRUE;
3168 break;
3169 case 404:
3171 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3172 if (warning != NULL) {
3173 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3174 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3175 g_free(reason);
3176 } else {
3177 warning = _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3180 sip->gc->wants_to_die = TRUE;
3181 purple_connection_error(sip->gc, warning);
3182 return TRUE;
3184 break;
3185 case 503:
3187 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3188 if (warning != NULL) {
3189 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3190 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3191 g_free(reason);
3192 } else {
3193 warning = _("Service unavailable: no reason given");
3196 sip->gc->wants_to_die = TRUE;
3197 purple_connection_error(sip->gc, warning);
3198 return TRUE;
3200 break;
3202 return TRUE;
3205 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3207 const char *uri;
3208 xmlnode *xn_categories;
3209 xmlnode *xn_category;
3210 xmlnode *xn_node;
3211 int changed = 0;
3212 const char *activity = NULL;
3214 xn_categories = xmlnode_from_str(data, len);
3215 uri = xmlnode_get_attrib(xn_categories, "uri");
3217 purple_debug_info("sipe", "process_incoming_notify_rlmi\n");
3219 for (xn_category = xmlnode_get_child(xn_categories, "category");
3220 xn_category ;
3221 xn_category = xmlnode_get_next_twin(xn_category) )
3223 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3225 if (!strcmp(attrVar, "note"))
3227 char *note;
3228 struct sipe_buddy *sbuddy;
3229 xn_node = xmlnode_get_child(xn_category, "note");
3230 if (!xn_node) continue;
3231 xn_node = xmlnode_get_child(xn_node, "body");
3232 if (!xn_node) continue;
3234 note = xmlnode_get_data(xn_node);
3236 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3237 if (sbuddy && note)
3239 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3240 sbuddy->annotation = g_strdup(note);
3241 changed = 1;
3244 g_free(note);
3246 else if(!strcmp(attrVar, "state"))
3248 char *data;
3249 int avail;
3250 xn_node = xmlnode_get_child(xn_category, "state");
3251 if (!xn_node) continue;
3252 xn_node = xmlnode_get_child(xn_node, "availability");
3253 if (!xn_node) continue;
3255 data = xmlnode_get_data(xn_node);
3256 avail = atoi(data);
3257 g_free(data);
3259 if (avail < 3000)
3260 activity = "unknown";
3261 else if (avail < 4500)
3262 activity = "available";
3263 else if (avail < 6000)
3264 activity = "idle";
3265 else if (avail < 7500)
3266 activity = "busy";
3267 else if (avail < 9000)
3268 activity = "busy";
3269 else if (avail < 12000)
3270 activity = "dnd";
3271 else if (avail < 18000)
3272 activity = "away";
3273 else
3274 activity = "offline";
3276 changed = 1;
3279 if (changed)
3281 if(activity){
3282 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3286 xmlnode_free(xn_categories);
3289 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3291 const char *uri,*state;
3292 xmlnode *xn_list;
3293 xmlnode *xn_resource;
3294 xmlnode *xn_instance;
3296 xn_list = xmlnode_from_str(data, len);
3298 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3299 xn_resource;
3300 xn_resource = xmlnode_get_next_twin(xn_resource) )
3302 xn_instance = xmlnode_get_child(xn_resource, "instance");
3303 if (!xn_instance) return;
3305 state = xmlnode_get_attrib(xn_instance, "state");
3306 uri = xmlnode_get_attrib(xn_instance, "cid");
3307 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3308 if(strstr(state,"resubscribe")){
3309 sipe_subscribe_presence_single(sip, uri);
3314 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3316 const gchar *uri;
3317 gchar *getbasic;
3318 gchar *activity = NULL;
3319 xmlnode *pidf;
3320 xmlnode *basicstatus = NULL, *tuple, *status;
3321 gboolean isonline = FALSE;
3322 xmlnode *display_name_node;
3324 pidf = xmlnode_from_str(data, len);
3325 if (!pidf) {
3326 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3327 return;
3330 uri = xmlnode_get_attrib(pidf, "entity");
3332 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3334 if ((status = xmlnode_get_child(tuple, "status"))) {
3335 basicstatus = xmlnode_get_child(status, "basic");
3339 if (!basicstatus) {
3340 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3341 xmlnode_free(pidf);
3342 return;
3345 getbasic = xmlnode_get_data(basicstatus);
3346 if (!getbasic) {
3347 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3348 xmlnode_free(pidf);
3349 return;
3352 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3353 if (strstr(getbasic, "open")) {
3354 isonline = TRUE;
3356 g_free(getbasic);
3358 display_name_node = xmlnode_get_child(pidf, "display-name");
3359 // updating display name if alias was just URI
3360 if (display_name_node) {
3361 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3362 GSList *entry = buddies;
3363 PurpleBuddy *p_buddy;
3364 char * display_name = xmlnode_get_data(display_name_node);
3366 while (entry) {
3367 const char *server_alias;
3368 char *alias;
3370 p_buddy = entry->data;
3372 alias = (char *)purple_buddy_get_alias(p_buddy);
3373 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3374 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3375 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3376 purple_blist_alias_buddy(p_buddy, display_name);
3378 g_free(alias);
3380 server_alias = purple_buddy_get_server_alias(p_buddy);
3381 if (display_name &&
3382 ( (server_alias && strcmp(display_name, server_alias))
3383 || !server_alias || strlen(server_alias) == 0 )
3385 purple_blist_server_alias_buddy(p_buddy, display_name);
3388 entry = entry->next;
3390 g_free(display_name);
3393 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3394 if ((status = xmlnode_get_child(tuple, "status"))) {
3395 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3396 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3397 activity = xmlnode_get_data(basicstatus);
3398 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3404 if (isonline) {
3405 gchar * status_id = NULL;
3406 if (activity) {
3407 if (strstr(activity, "busy")) {
3408 status_id = "busy";
3409 } else if (strstr(activity, "away")) {
3410 status_id = "away";
3414 if (!status_id) {
3415 status_id = "available";
3418 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3419 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3420 } else {
3421 purple_prpl_got_user_status(sip->account, uri, "offline", NULL);
3424 g_free(activity);
3425 xmlnode_free(pidf);
3428 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3430 const char *availability;
3431 const char *activity;
3432 const char *display_name = NULL;
3433 const char *activity_name;
3434 const char *name;
3435 char *uri;
3436 int avl;
3437 int act;
3438 struct sipe_buddy *sbuddy;
3440 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3442 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3443 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3444 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3445 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3446 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3447 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3448 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3449 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3450 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3451 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3452 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3453 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3455 name = xmlnode_get_attrib(xn_presentity, "uri");
3456 uri = g_strdup_printf("sip:%s", name);
3457 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3458 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3460 // updating display name if alias was just URI
3461 if (xn_display_name) {
3462 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3463 GSList *entry = buddies;
3464 PurpleBuddy *p_buddy;
3465 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3467 while (entry) {
3468 const char *email_str, *server_alias;
3470 p_buddy = entry->data;
3472 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3473 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3474 purple_blist_alias_buddy(p_buddy, display_name);
3477 server_alias = purple_buddy_get_server_alias(p_buddy);
3478 if (display_name &&
3479 ( (server_alias && strcmp(display_name, server_alias))
3480 || !server_alias || strlen(server_alias) == 0 )
3482 purple_blist_server_alias_buddy(p_buddy, display_name);
3485 if (email) {
3486 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3487 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3488 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3492 entry = entry->next;
3496 avl = atoi(availability);
3497 act = atoi(activity);
3499 if (act <= 100)
3500 activity_name = "away";
3501 else if (act <= 150)
3502 activity_name = "out-to-lunch";
3503 else if (act <= 300)
3504 activity_name = "be-right-back";
3505 else if (act <= 400)
3506 activity_name = "available";
3507 else if (act <= 500)
3508 activity_name = "on-the-phone";
3509 else if (act <= 600)
3510 activity_name = "busy";
3511 else
3512 activity_name = "available";
3514 if (avl == 0)
3515 activity_name = "offline";
3517 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3518 if (sbuddy)
3520 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3521 sbuddy->annotation = NULL;
3522 if (note) { sbuddy->annotation = g_strdup(note); }
3524 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3525 sbuddy->device_name = NULL;
3526 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3529 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3530 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3531 g_free(note);
3532 xmlnode_free(xn_presentity);
3533 g_free(uri);
3536 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3538 char *ctype = sipmsg_find_header(msg, "Content-Type");
3540 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3542 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3543 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3545 const char *content = msg->body;
3546 unsigned length = msg->bodylen;
3547 PurpleMimeDocument *mime = NULL;
3549 if (strstr(ctype, "multipart"))
3551 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3552 const char *content_type;
3553 GList* parts;
3554 mime = purple_mime_document_parse(doc);
3555 parts = purple_mime_document_get_parts(mime);
3556 while(parts) {
3557 content = purple_mime_part_get_data(parts->data);
3558 length = purple_mime_part_get_length(parts->data);
3559 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3560 if(content_type && strstr(content_type,"application/rlmi+xml"))
3562 process_incoming_notify_rlmi_resub(sip, content, length);
3564 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3566 process_incoming_notify_msrtc(sip, content, length);
3568 else
3570 process_incoming_notify_rlmi(sip, content, length);
3572 parts = parts->next;
3574 g_free(doc);
3576 if (mime)
3578 purple_mime_document_free(mime);
3581 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3583 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3585 else if(strstr(ctype, "application/rlmi+xml"))
3587 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3590 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3592 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3594 else
3596 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3601 * Dispatcher for all incoming subscription information
3602 * whether it comes from NOTIFY, BENOTIFY requests or
3603 * piggy-backed to subscription's OK responce.
3605 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3606 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3608 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3610 gchar *event = sipmsg_find_header(msg, "Event");
3611 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3612 const char *uri,*state;
3613 xmlnode *xn_list;
3614 xmlnode *xn_resource;
3615 xmlnode *xn_instance;
3617 int expires = 0;
3619 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3620 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3622 if (!request)
3624 const gchar *expires_header;
3625 expires_header = sipmsg_find_header(msg, "Expires");
3626 expires = expires_header ? strtol(expires_header, NULL, 10) : 0;
3627 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires);
3630 if (!subscription_state || strstr(subscription_state, "active"))
3632 if (event && !g_ascii_strcasecmp(event, "presence"))
3634 sipe_process_presence(sip, msg);
3636 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3638 sipe_process_roaming_contacts(sip, msg, NULL);
3640 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
3642 sipe_process_roaming_self(sip, msg);
3644 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
3646 //@TODO
3647 purple_debug_info("sipe", "vnd-microsoft-provisioning-v2 data is not supported yet.");
3649 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3651 sipe_process_roaming_acl(sip, msg);
3653 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3655 sipe_process_presence_wpending(sip, msg);
3657 else
3659 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3663 //The server sends a (BE)NOTIFY with the status 'terminated'
3664 if(request && subscription_state && strstr(subscription_state, "terminated") )
3666 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3667 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3668 g_free(from);
3671 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3672 if (request && !benotify)
3674 sipmsg_remove_header(msg, "Expires");
3675 sipmsg_remove_header(msg, "subscription-state");
3676 sipmsg_remove_header(msg, "Event");
3677 sipmsg_remove_header(msg, "Require");
3678 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3683 * unused. Needed?
3685 static gchar* gen_xpidf(struct sipe_account_data *sip)
3687 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3688 "<presence>\r\n"
3689 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3690 "<display name=\"sip:%s\"/>\r\n"
3691 "<atom id=\"1234\">\r\n"
3692 "<address uri=\"sip:%s\">\r\n"
3693 "<status status=\"%s\"/>\r\n"
3694 "</address>\r\n"
3695 "</atom>\r\n"
3696 "</presence>\r\n",
3697 sip->username,
3698 sip->username,
3699 sip->username,
3700 sip->status);
3701 return doc;
3706 static gchar* gen_pidf(struct sipe_account_data *sip)
3708 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3709 "<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"
3710 "<tuple id=\"0\">\r\n"
3711 "<status>\r\n"
3712 "<basic>open</basic>\r\n"
3713 "<ep:activities>\r\n"
3714 " <ep:activity>%s</ep:activity>\r\n"
3715 "</ep:activities>"
3716 "</status>\r\n"
3717 "</tuple>\r\n"
3718 "<ci:display-name>%s</ci:display-name>\r\n"
3719 "</presence>",
3720 sip->username,
3721 sip->status,
3722 sip->username);
3723 return doc;
3727 static gboolean
3728 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3730 if (msg->response == 488) {
3731 sip->presence_method_version = 1;
3732 send_presence_info(sip);
3734 return TRUE;
3737 static void send_presence_info_v0(struct sipe_account_data *sip, const char * note)
3739 int availability = 300; // online
3740 int activity = 400; // Available
3741 gchar *name;
3742 gchar *body;
3743 if (!strcmp(sip->status, "away")) {
3744 activity = 100;
3745 } else if (!strcmp(sip->status, "out-to-lunch")) {
3746 activity = 150;
3747 } else if (!strcmp(sip->status, "be-right-back")) {
3748 activity = 300;
3749 } else if (!strcmp(sip->status, "on-the-phone")) {
3750 activity = 500;
3751 } else if (!strcmp(sip->status, "do-not-disturb")) {
3752 activity = 600;
3753 } else if (!strcmp(sip->status, "busy")) {
3754 activity = 600;
3755 } else if (!strcmp(sip->status, "invisible")) {
3756 availability = 0; // offline
3757 activity = 100;
3760 name = g_strdup_printf("sip: sip:%s", sip->username);
3761 //@TODO: send user data - state; add hostname in upper case
3762 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3763 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
3764 g_free(name);
3765 g_free(body);
3768 static gboolean
3769 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3771 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3772 if (msg->response == 200) {
3773 sip->status_version = 0;
3774 send_presence_info(sip);
3776 return TRUE;
3779 static gboolean
3780 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3782 if (msg->response == 409) {
3783 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3784 // TODO need to parse the version #'s?
3785 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3786 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3787 gchar *tmp;
3788 gchar *hdr;
3790 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg->body);
3792 tmp = get_contact(sip);
3793 hdr = g_strdup_printf("Contact: %s\r\n"
3794 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3796 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3798 g_free(tmp);
3799 g_free(hdr);
3800 g_free(uri);
3801 g_free(doc);
3803 return TRUE;
3806 static void send_presence_info_v1(struct sipe_account_data *sip, const char * note)
3808 int code;
3809 gchar *uri;
3810 gchar *doc;
3811 gchar *tmp;
3812 gchar *hdr;
3813 if (!strcmp(sip->status, "away")) {
3814 code = 12000;
3815 } else if (!strcmp(sip->status, "busy")) {
3816 code = 6000;
3817 } else {
3818 // Available
3819 code = 3000;
3822 uri = g_strdup_printf("sip:%s", sip->username);
3823 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3824 sip->status_version, code,
3825 sip->status_version, code,
3826 sip->status_version, note ? note : "",
3827 sip->status_version, note ? note : "",
3828 sip->status_version, note ? note : ""
3830 sip->status_version++;
3832 tmp = get_contact(sip);
3833 hdr = g_strdup_printf("Contact: %s\r\n"
3834 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3836 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
3838 g_free(tmp);
3839 g_free(hdr);
3840 g_free(uri);
3841 g_free(doc);
3844 static void send_presence_info(struct sipe_account_data *sip)
3846 PurpleStatus * status = purple_account_get_active_status(sip->account);
3847 const gchar *note;
3848 if (!status) return;
3850 note = purple_status_get_attr_string(status, "message");
3852 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
3853 if (sip->presence_method_version != 1) {
3854 send_presence_info_v0(sip, note);
3855 } else {
3856 send_presence_info_v1(sip, note);
3860 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3862 gboolean found = FALSE;
3863 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3864 if (msg->response == 0) { /* request */
3865 if (!strcmp(msg->method, "MESSAGE")) {
3866 process_incoming_message(sip, msg);
3867 found = TRUE;
3868 } else if (!strcmp(msg->method, "NOTIFY")) {
3869 purple_debug_info("sipe","send->process_incoming_notify\n");
3870 process_incoming_notify(sip, msg, TRUE, FALSE);
3871 found = TRUE;
3872 } else if (!strcmp(msg->method, "BENOTIFY")) {
3873 purple_debug_info("sipe","send->process_incoming_benotify\n");
3874 process_incoming_notify(sip, msg, TRUE, TRUE);
3875 found = TRUE;
3876 } else if (!strcmp(msg->method, "INVITE")) {
3877 process_incoming_invite(sip, msg);
3878 found = TRUE;
3879 } else if (!strcmp(msg->method, "OPTIONS")) {
3880 process_incoming_options(sip, msg);
3881 found = TRUE;
3882 } else if (!strcmp(msg->method, "INFO")) {
3883 // TODO needs work
3884 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3885 if (from) {
3886 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3888 g_free(from);
3889 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3890 found = TRUE;
3891 } else if (!strcmp(msg->method, "ACK")) {
3892 // ACK's don't need any response
3893 found = TRUE;
3894 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3895 // LCS 2005 sends us these - just respond 200 OK
3896 found = TRUE;
3897 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3898 } else if (!strcmp(msg->method, "BYE")) {
3899 struct sip_im_session *session;
3900 gchar *from;
3901 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3903 from = parse_from(sipmsg_find_header(msg, "From"));
3904 session = find_im_session (sip, from);
3905 g_free(from);
3907 if (session) {
3908 // TODO Let the user know the other user left the conversation?
3909 im_session_destroy(sip, session);
3912 found = TRUE;
3913 } else {
3914 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3916 } else { /* response */
3917 struct transaction *trans = transactions_find(sip, msg);
3918 if (trans) {
3919 if (msg->response == 407) {
3920 gchar *resend, *auth, *ptmp;
3922 if (sip->proxy.retries > 30) return;
3923 sip->proxy.retries++;
3924 /* do proxy authentication */
3926 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3928 fill_auth(sip, ptmp, &sip->proxy);
3929 auth = auth_header(sip, &sip->proxy, trans->msg);
3930 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3931 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
3932 g_free(auth);
3933 resend = sipmsg_to_string(trans->msg);
3934 /* resend request */
3935 sendout_pkt(sip->gc, resend);
3936 g_free(resend);
3937 } else {
3938 if (msg->response == 100 || msg->response == 180) {
3939 /* ignore provisional response */
3940 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
3941 } else {
3942 sip->proxy.retries = 0;
3943 if (!strcmp(trans->msg->method, "REGISTER")) {
3944 if (msg->response == 401)
3946 sip->registrar.retries++;
3947 sip->registrar.expires = 0;
3949 else
3951 sip->registrar.retries = 0;
3953 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
3954 } else {
3955 if (msg->response == 401) {
3956 gchar *resend, *auth, *ptmp;
3958 if (sip->registrar.retries > 4) return;
3959 sip->registrar.retries++;
3961 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3962 ptmp = sipmsg_find_auth_header(msg, "NTLM");
3963 } else {
3964 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
3967 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
3969 fill_auth(sip, ptmp, &sip->registrar);
3970 auth = auth_header(sip, &sip->registrar, trans->msg);
3971 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3972 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
3974 //sipmsg_remove_header(trans->msg, "Authorization");
3975 //sipmsg_add_header(trans->msg, "Authorization", auth);
3976 g_free(auth);
3977 resend = sipmsg_to_string(trans->msg);
3978 /* resend request */
3979 sendout_pkt(sip->gc, resend);
3980 g_free(resend);
3984 if (trans->callback) {
3985 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
3986 /* call the callback to process response*/
3987 (trans->callback)(sip, msg, trans);
3989 /* Not sure if this is needed or what needs to be done
3990 but transactions seem to be removed prematurely so
3991 this only removes them if the response is 200 OK */
3992 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
3993 /*Has a bug and it's unneccesary*/
3994 /*transactions_remove(sip, trans);*/
3998 found = TRUE;
3999 } else {
4000 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
4003 if (!found) {
4004 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4008 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4010 char *cur;
4011 char *dummy;
4012 struct sipmsg *msg;
4013 int restlen;
4014 cur = conn->inbuf;
4016 /* according to the RFC remove CRLF at the beginning */
4017 while (*cur == '\r' || *cur == '\n') {
4018 cur++;
4020 if (cur != conn->inbuf) {
4021 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4022 conn->inbufused = strlen(conn->inbuf);
4025 /* Received a full Header? */
4026 sip->processing_input = TRUE;
4027 while (sip->processing_input &&
4028 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4029 time_t currtime = time(NULL);
4030 cur += 2;
4031 cur[0] = '\0';
4032 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4033 msg = sipmsg_parse_header(conn->inbuf);
4034 cur[0] = '\r';
4035 cur += 2;
4036 restlen = conn->inbufused - (cur - conn->inbuf);
4037 if (restlen >= msg->bodylen) {
4038 dummy = g_malloc(msg->bodylen + 1);
4039 memcpy(dummy, cur, msg->bodylen);
4040 dummy[msg->bodylen] = '\0';
4041 msg->body = dummy;
4042 cur += msg->bodylen;
4043 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4044 conn->inbufused = strlen(conn->inbuf);
4045 } else {
4046 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4047 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4048 sipmsg_free(msg);
4049 return;
4052 /*if (msg->body) {
4053 purple_debug_info("sipe", "body:\n%s", msg->body);
4056 // Verify the signature before processing it
4057 if (sip->registrar.ntlm_key) {
4058 struct sipmsg_breakdown msgbd;
4059 gchar *signature_input_str;
4060 gchar *signature = NULL;
4061 gchar *rspauth;
4062 msgbd.msg = msg;
4063 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4064 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4065 if (signature_input_str != NULL) {
4066 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4068 g_free(signature_input_str);
4070 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4072 if (signature != NULL) {
4073 if (rspauth != NULL) {
4074 if (purple_ntlm_verify_signature (signature, rspauth)) {
4075 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4076 process_input_message(sip, msg);
4077 } else {
4078 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4079 purple_connection_error(sip->gc, _("Invalid message signature received"));
4080 sip->gc->wants_to_die = TRUE;
4082 } else if (msg->response == 401) {
4083 purple_connection_error(sip->gc, _("Wrong Password"));
4084 sip->gc->wants_to_die = TRUE;
4086 g_free(signature);
4089 g_free(rspauth);
4090 sipmsg_breakdown_free(&msgbd);
4091 } else {
4092 process_input_message(sip, msg);
4095 sipmsg_free(msg);
4099 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4101 PurpleConnection *gc = data;
4102 struct sipe_account_data *sip = gc->proto_data;
4103 struct sipmsg *msg;
4104 int len;
4105 time_t currtime;
4107 static char buffer[65536];
4108 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4109 buffer[len] = '\0';
4110 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4111 msg = sipmsg_parse_msg(buffer);
4112 if (msg) process_input_message(sip, msg);
4116 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4118 struct sipe_account_data *sip = gc->proto_data;
4119 PurpleSslConnection *gsc = sip->gsc;
4121 purple_debug_error("sipe", "%s",debug);
4122 purple_connection_error(gc, msg);
4124 /* Invalidate this connection. Next send will open a new one */
4125 if (gsc) {
4126 connection_remove(sip, gsc->fd);
4127 purple_ssl_close(gsc);
4129 sip->gsc = NULL;
4130 sip->fd = -1;
4133 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4135 PurpleConnection *gc = data;
4136 struct sipe_account_data *sip;
4137 struct sip_connection *conn;
4138 int readlen, len;
4139 gboolean firstread = TRUE;
4141 /* NOTE: This check *IS* necessary */
4142 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4143 purple_ssl_close(gsc);
4144 return;
4147 sip = gc->proto_data;
4148 conn = connection_find(sip, gsc->fd);
4149 if (conn == NULL) {
4150 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4151 gc->wants_to_die = TRUE;
4152 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4153 return;
4156 /* Read all available data from the SSL connection */
4157 do {
4158 /* Increase input buffer size as needed */
4159 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4160 conn->inbuflen += SIMPLE_BUF_INC;
4161 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4162 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4165 /* Try to read as much as there is space left in the buffer */
4166 readlen = conn->inbuflen - conn->inbufused - 1;
4167 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4169 if (len < 0 && errno == EAGAIN) {
4170 /* Try again later */
4171 return;
4172 } else if (len < 0) {
4173 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4174 return;
4175 } else if (firstread && (len == 0)) {
4176 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4177 return;
4180 conn->inbufused += len;
4181 firstread = FALSE;
4183 /* Equivalence indicates that there is possibly more data to read */
4184 } while (len == readlen);
4186 conn->inbuf[conn->inbufused] = '\0';
4187 process_input(sip, conn);
4191 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4193 PurpleConnection *gc = data;
4194 struct sipe_account_data *sip = gc->proto_data;
4195 int len;
4196 struct sip_connection *conn = connection_find(sip, source);
4197 if (!conn) {
4198 purple_debug_error("sipe", "Connection not found!\n");
4199 return;
4202 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4203 conn->inbuflen += SIMPLE_BUF_INC;
4204 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4207 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4209 if (len < 0 && errno == EAGAIN)
4210 return;
4211 else if (len <= 0) {
4212 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4213 connection_remove(sip, source);
4214 if (sip->fd == source) sip->fd = -1;
4215 return;
4218 conn->inbufused += len;
4219 conn->inbuf[conn->inbufused] = '\0';
4221 process_input(sip, conn);
4224 /* Callback for new connections on incoming TCP port */
4225 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4227 PurpleConnection *gc = data;
4228 struct sipe_account_data *sip = gc->proto_data;
4229 struct sip_connection *conn;
4231 int newfd = accept(source, NULL, NULL);
4233 conn = connection_create(sip, newfd);
4235 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4238 static void login_cb(gpointer data, gint source, const gchar *error_message)
4240 PurpleConnection *gc = data;
4241 struct sipe_account_data *sip;
4242 struct sip_connection *conn;
4244 if (!PURPLE_CONNECTION_IS_VALID(gc))
4246 if (source >= 0)
4247 close(source);
4248 return;
4251 if (source < 0) {
4252 purple_connection_error(gc, _("Could not connect"));
4253 return;
4256 sip = gc->proto_data;
4257 sip->fd = source;
4258 sip->last_keepalive = time(NULL);
4260 conn = connection_create(sip, source);
4262 do_register(sip);
4264 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4267 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4269 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4270 if (sip == NULL) return;
4272 do_register(sip);
4275 static guint sipe_ht_hash_nick(const char *nick)
4277 char *lc = g_utf8_strdown(nick, -1);
4278 guint bucket = g_str_hash(lc);
4279 g_free(lc);
4281 return bucket;
4284 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4286 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4289 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4291 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4293 sip->listen_data = NULL;
4295 if (listenfd == -1) {
4296 purple_connection_error(sip->gc, _("Could not create listen socket"));
4297 return;
4300 sip->fd = listenfd;
4302 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4303 sip->listenfd = sip->fd;
4305 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4307 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4308 do_register(sip);
4311 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4313 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4314 int addr_size;
4316 sip->query_data = NULL;
4318 if (!hosts || !hosts->data) {
4319 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4320 return;
4323 addr_size = GPOINTER_TO_INT(hosts->data);
4324 hosts = g_slist_remove(hosts, hosts->data);
4325 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4326 g_free(hosts->data);
4327 hosts = g_slist_remove(hosts, hosts->data);
4328 while (hosts) {
4329 hosts = g_slist_remove(hosts, hosts->data);
4330 g_free(hosts->data);
4331 hosts = g_slist_remove(hosts, hosts->data);
4334 /* create socket for incoming connections */
4335 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4336 sipe_udp_host_resolved_listen_cb, sip);
4337 if (sip->listen_data == NULL) {
4338 purple_connection_error(sip->gc, _("Could not create listen socket"));
4339 return;
4343 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4344 gpointer data)
4346 PurpleConnection *gc = data;
4347 struct sipe_account_data *sip;
4349 /* If the connection is already disconnected, we don't need to do anything else */
4350 if (!PURPLE_CONNECTION_IS_VALID(gc))
4351 return;
4353 sip = gc->proto_data;
4354 sip->fd = -1;
4355 sip->gsc = NULL;
4357 switch(error) {
4358 case PURPLE_SSL_CONNECT_FAILED:
4359 purple_connection_error(gc, _("Connection Failed"));
4360 break;
4361 case PURPLE_SSL_HANDSHAKE_FAILED:
4362 purple_connection_error(gc, _("SSL Handshake Failed"));
4363 break;
4364 case PURPLE_SSL_CERTIFICATE_INVALID:
4365 purple_connection_error(gc, _("SSL Certificate Invalid"));
4366 break;
4370 static void
4371 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4373 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4374 PurpleProxyConnectData *connect_data;
4376 sip->listen_data = NULL;
4378 sip->listenfd = listenfd;
4379 if (sip->listenfd == -1) {
4380 purple_connection_error(sip->gc, _("Could not create listen socket"));
4381 return;
4384 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4385 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4386 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4387 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4388 sipe_newconn_cb, sip->gc);
4389 purple_debug_info("sipe", "connecting to %s port %d\n",
4390 sip->realhostname, sip->realport);
4391 /* open tcp connection to the server */
4392 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4393 sip->realport, login_cb, sip->gc);
4395 if (connect_data == NULL) {
4396 purple_connection_error(sip->gc, _("Couldn't create socket"));
4401 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4403 PurpleAccount *account = sip->account;
4404 PurpleConnection *gc = sip->gc;
4406 if (purple_account_get_bool(account, "useport", FALSE)) {
4407 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4408 port = purple_account_get_int(account, "port", 0);
4409 } else {
4410 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4413 sip->realhostname = hostname;
4414 sip->realport = port;
4416 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4417 hostname, port);
4419 /* TODO: is there a good default grow size? */
4420 if (sip->transport != SIPE_TRANSPORT_UDP)
4421 sip->txbuf = purple_circ_buffer_new(0);
4423 if (sip->transport == SIPE_TRANSPORT_TLS) {
4424 /* SSL case */
4425 if (!purple_ssl_is_supported()) {
4426 gc->wants_to_die = TRUE;
4427 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4428 return;
4431 purple_debug_info("sipe", "using SSL\n");
4433 sip->gsc = purple_ssl_connect(account, hostname, port,
4434 login_cb_ssl, sipe_ssl_connect_failure, gc);
4435 if (sip->gsc == NULL) {
4436 purple_connection_error(gc, _("Could not create SSL context"));
4437 return;
4439 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4440 /* UDP case */
4441 purple_debug_info("sipe", "using UDP\n");
4443 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4444 if (sip->query_data == NULL) {
4445 purple_connection_error(gc, _("Could not resolve hostname"));
4447 } else {
4448 /* TCP case */
4449 purple_debug_info("sipe", "using TCP\n");
4450 /* create socket for incoming connections */
4451 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4452 sipe_tcp_connect_listen_cb, sip);
4453 if (sip->listen_data == NULL) {
4454 purple_connection_error(gc, _("Could not create listen socket"));
4455 return;
4460 /* Service list for autodection */
4461 static const struct sipe_service_data service_autodetect[] = {
4462 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4463 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4464 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4465 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4466 { NULL, NULL, 0 }
4469 /* Service list for SSL/TLS */
4470 static const struct sipe_service_data service_tls[] = {
4471 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4472 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4473 { NULL, NULL, 0 }
4476 /* Service list for TCP */
4477 static const struct sipe_service_data service_tcp[] = {
4478 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4479 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4480 { NULL, NULL, 0 }
4483 /* Service list for UDP */
4484 static const struct sipe_service_data service_udp[] = {
4485 { "sip", "udp", SIPE_TRANSPORT_UDP },
4486 { NULL, NULL, 0 }
4489 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4490 static void resolve_next_service(struct sipe_account_data *sip,
4491 const struct sipe_service_data *start)
4493 if (start) {
4494 sip->service_data = start;
4495 } else {
4496 sip->service_data++;
4497 if (sip->service_data->service == NULL) {
4498 gchar *hostname;
4499 /* Try connecting to the SIP hostname directly */
4500 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4501 if (sip->auto_transport) {
4502 // If SSL is supported, default to using it; OCS servers aren't configured
4503 // by default to accept TCP
4504 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4505 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4506 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4509 hostname = g_strdup(sip->sipdomain);
4510 create_connection(sip, hostname, 0);
4511 return;
4515 /* Try to resolve next service */
4516 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4517 sip->service_data->transport,
4518 sip->sipdomain,
4519 srvresolved, sip);
4522 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4524 struct sipe_account_data *sip = data;
4526 sip->srv_query_data = NULL;
4528 /* find the host to connect to */
4529 if (results) {
4530 gchar *hostname = g_strdup(resp->hostname);
4531 int port = resp->port;
4532 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4533 hostname, port);
4534 g_free(resp);
4536 sip->transport = sip->service_data->type;
4538 create_connection(sip, hostname, port);
4539 } else {
4540 resolve_next_service(sip, NULL);
4544 static void sipe_login(PurpleAccount *account)
4546 PurpleConnection *gc;
4547 struct sipe_account_data *sip;
4548 gchar **signinname_login, **userserver, **domain_user;
4549 const char *transport;
4551 const char *username = purple_account_get_username(account);
4552 gc = purple_account_get_connection(account);
4554 if (strpbrk(username, " \t\v\r\n") != NULL) {
4555 gc->wants_to_die = TRUE;
4556 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4557 return;
4560 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4561 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4562 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4563 sip->gc = gc;
4564 sip->account = account;
4565 sip->reregister_set = FALSE;
4566 sip->reauthenticate_set = FALSE;
4567 sip->subscribed = FALSE;
4568 sip->subscribed_buddies = FALSE;
4570 signinname_login = g_strsplit(username, ",", 2);
4572 userserver = g_strsplit(signinname_login[0], "@", 2);
4573 purple_connection_set_display_name(gc, userserver[0]);
4574 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4575 sip->sipdomain = g_strdup(userserver[1]);
4577 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4578 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4579 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4581 sip->password = g_strdup(purple_connection_get_password(gc));
4583 g_strfreev(userserver);
4584 g_strfreev(domain_user);
4585 g_strfreev(signinname_login);
4587 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4589 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4591 /* TODO: Set the status correctly. */
4592 sip->status = g_strdup("available");
4594 transport = purple_account_get_string(account, "transport", "auto");
4595 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4596 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4597 SIPE_TRANSPORT_UDP;
4599 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4600 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4601 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4602 } else if (strcmp(transport, "auto") == 0) {
4603 sip->auto_transport = TRUE;
4604 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4605 } else if (strcmp(transport, "tls") == 0) {
4606 resolve_next_service(sip, service_tls);
4607 } else if (strcmp(transport, "tcp") == 0) {
4608 resolve_next_service(sip, service_tcp);
4609 } else {
4610 resolve_next_service(sip, service_udp);
4614 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4616 connection_free_all(sip);
4618 if (sip->query_data != NULL)
4619 purple_dnsquery_destroy(sip->query_data);
4620 sip->query_data = NULL;
4622 if (sip->srv_query_data != NULL)
4623 purple_srv_cancel(sip->srv_query_data);
4624 sip->srv_query_data = NULL;
4626 if (sip->listen_data != NULL)
4627 purple_network_listen_cancel(sip->listen_data);
4628 sip->listen_data = NULL;
4630 if (sip->gsc != NULL)
4631 purple_ssl_close(sip->gsc);
4632 sip->gsc = NULL;
4634 sipe_auth_free(&sip->registrar);
4635 sipe_auth_free(&sip->proxy);
4637 if (sip->txbuf)
4638 purple_circ_buffer_destroy(sip->txbuf);
4639 sip->txbuf = NULL;
4641 g_free(sip->realhostname);
4642 sip->realhostname = NULL;
4644 if (sip->listenpa)
4645 purple_input_remove(sip->listenpa);
4646 sip->listenpa = 0;
4647 if (sip->tx_handler)
4648 purple_input_remove(sip->tx_handler);
4649 sip->tx_handler = 0;
4650 if (sip->resendtimeout)
4651 purple_timeout_remove(sip->resendtimeout);
4652 sip->resendtimeout = 0;
4653 if (sip->timeouts) {
4654 GSList *entry = sip->timeouts;
4655 while (entry) {
4656 struct scheduled_action *sched_action = entry->data;
4657 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4658 purple_timeout_remove(sched_action->timeout_handler);
4659 g_free(sched_action->payload);
4660 g_free(sched_action->name);
4661 g_free(sched_action);
4662 entry = entry->next;
4665 g_slist_free(sip->timeouts);
4667 g_slist_free(sip->allow_events);
4669 if (sip->contact)
4670 g_free(sip->contact);
4671 sip->contact = NULL;
4672 if (sip->regcallid)
4673 g_free(sip->regcallid);
4674 sip->regcallid = NULL;
4676 sip->fd = -1;
4677 sip->processing_input = FALSE;
4681 * A callback for g_hash_table_foreach_remove
4683 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4685 sipe_free_buddy(buddy);
4688 static void sipe_close(PurpleConnection *gc)
4690 struct sipe_account_data *sip = gc->proto_data;
4692 if (sip) {
4693 /* leave all conversations */
4694 im_session_close_all(sip);
4696 /* unregister */
4697 do_register_exp(sip, 0);
4699 sipe_connection_cleanup(sip);
4700 g_free(sip->sipdomain);
4701 g_free(sip->username);
4702 g_free(sip->password);
4703 g_free(sip->authdomain);
4704 g_free(sip->authuser);
4705 g_free(sip->status);
4707 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4708 g_hash_table_destroy(sip->buddies);
4710 g_free(gc->proto_data);
4711 gc->proto_data = NULL;
4714 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4716 PurpleAccount *acct = purple_connection_get_account(gc);
4717 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4718 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4719 if (conv == NULL)
4720 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4721 purple_conversation_present(conv);
4722 g_free(id);
4725 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4728 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4729 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4732 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4734 PurpleNotifySearchResults *results;
4735 PurpleNotifySearchColumn *column;
4736 xmlnode *searchResults;
4737 xmlnode *mrow;
4738 int match_count = 0;
4739 gboolean more = FALSE;
4740 gchar *secondary;
4742 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
4744 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4745 if (!searchResults) {
4746 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4747 return FALSE;
4750 results = purple_notify_searchresults_new();
4752 if (results == NULL) {
4753 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4754 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4756 xmlnode_free(searchResults);
4757 return FALSE;
4760 column = purple_notify_searchresults_column_new(_("User Name"));
4761 purple_notify_searchresults_column_add(results, column);
4763 column = purple_notify_searchresults_column_new(_("Name"));
4764 purple_notify_searchresults_column_add(results, column);
4766 column = purple_notify_searchresults_column_new(_("Company"));
4767 purple_notify_searchresults_column_add(results, column);
4769 column = purple_notify_searchresults_column_new(_("Country"));
4770 purple_notify_searchresults_column_add(results, column);
4772 column = purple_notify_searchresults_column_new(_("Email"));
4773 purple_notify_searchresults_column_add(results, column);
4775 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4776 GList *row = NULL;
4778 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4779 row = g_list_append(row, g_strdup(uri_parts[1]));
4780 g_strfreev(uri_parts);
4782 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4783 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4784 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4785 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4787 purple_notify_searchresults_row_add(results, row);
4788 match_count++;
4791 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4792 char *data = xmlnode_get_data_unescaped(mrow);
4793 more = (g_strcasecmp(data, "true") == 0);
4794 g_free(data);
4797 secondary = g_strdup_printf(
4798 dngettext(GETTEXT_PACKAGE,
4799 "Found %d contact%s:",
4800 "Found %d contacts%s:", match_count),
4801 match_count, more ? _(" (more matched your query)") : "");
4803 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4804 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4805 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4807 g_free(secondary);
4808 xmlnode_free(searchResults);
4809 return TRUE;
4812 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4814 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4815 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4816 unsigned i = 0;
4818 do {
4819 PurpleRequestField *field = entries->data;
4820 const char *id = purple_request_field_get_id(field);
4821 const char *value = purple_request_field_string_get_value(field);
4823 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
4825 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
4826 } while ((entries = g_list_next(entries)) != NULL);
4827 attrs[i] = NULL;
4829 if (i > 0) {
4830 gchar *query = g_strjoinv(NULL, attrs);
4831 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
4832 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
4833 send_soap_request_with_cb(gc->proto_data, body,
4834 (TransCallback) process_search_contact_response, NULL);
4835 g_free(body);
4836 g_free(query);
4839 g_strfreev(attrs);
4842 static void sipe_show_find_contact(PurplePluginAction *action)
4844 PurpleConnection *gc = (PurpleConnection *) action->context;
4845 PurpleRequestFields *fields;
4846 PurpleRequestFieldGroup *group;
4847 PurpleRequestField *field;
4849 fields = purple_request_fields_new();
4850 group = purple_request_field_group_new(NULL);
4851 purple_request_fields_add_group(fields, group);
4853 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4854 purple_request_field_group_add_field(group, field);
4855 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4856 purple_request_field_group_add_field(group, field);
4857 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4858 purple_request_field_group_add_field(group, field);
4859 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4860 purple_request_field_group_add_field(group, field);
4862 purple_request_fields(gc,
4863 _("Search"),
4864 _("Search for a Contact"),
4865 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4866 fields,
4867 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4868 _("_Cancel"), NULL,
4869 purple_connection_get_account(gc), NULL, NULL, gc);
4872 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4874 GList *menu = NULL;
4875 PurplePluginAction *act;
4877 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4878 menu = g_list_prepend(menu, act);
4880 menu = g_list_reverse(menu);
4882 return menu;
4885 static void dummy_permit_deny(PurpleConnection *gc)
4889 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4891 return TRUE;
4895 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4897 return TRUE;
4901 static char *sipe_status_text(PurpleBuddy *buddy)
4903 struct sipe_account_data *sip;
4904 struct sipe_buddy *sbuddy;
4905 char *text = NULL;
4907 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4908 if (sip) //happens on pidgin exit
4910 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4911 if (sbuddy && sbuddy->annotation)
4913 text = g_strdup(sbuddy->annotation);
4917 return text;
4920 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4922 const PurplePresence *presence = purple_buddy_get_presence(buddy);
4923 const PurpleStatus *status = purple_presence_get_active_status(presence);
4924 struct sipe_account_data *sip;
4925 struct sipe_buddy *sbuddy;
4926 char *annotation = NULL;
4928 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4929 if (sip) //happens on pidgin exit
4931 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4932 if (sbuddy)
4934 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
4938 //Layout
4939 if (purple_presence_is_online(presence))
4941 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
4944 if (annotation)
4946 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
4947 g_free(annotation);
4952 static GHashTable *
4953 sipe_get_account_text_table(PurpleAccount *account)
4955 GHashTable *table;
4956 table = g_hash_table_new(g_str_hash, g_str_equal);
4957 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
4958 return table;
4961 static PurpleBuddy *
4962 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
4964 PurpleBuddy *clone;
4965 const gchar *server_alias, *email;
4966 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
4968 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
4970 purple_blist_add_buddy(clone, NULL, group, NULL);
4972 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
4973 if (server_alias) {
4974 purple_blist_server_alias_buddy(clone, server_alias);
4977 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4978 if (email) {
4979 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
4982 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
4983 //for UI to update;
4984 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
4985 return clone;
4988 static void
4989 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
4991 PurpleBuddy *buddy, *b;
4992 PurpleConnection *gc;
4993 PurpleGroup * group = purple_find_group(group_name);
4995 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
4997 buddy = (PurpleBuddy *)node;
4999 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5000 gc = purple_account_get_connection(buddy->account);
5002 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5003 if (!b){
5004 b = purple_blist_add_buddy_clone(group, buddy);
5007 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5010 static void
5011 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5013 const gchar *email;
5014 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5016 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5017 if (email)
5019 char *mailto = g_strdup_printf("mailto:%s", email);
5020 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5021 #ifndef _WIN32
5023 pid_t pid;
5024 char *const parmList[] = {mailto, NULL};
5025 if ((pid = fork()) == -1)
5027 purple_debug_info("sipe", "fork() error\n");
5029 else if (pid == 0)
5031 execvp("xdg-email", parmList);
5032 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5035 #else
5037 BOOL ret;
5038 _flushall();
5039 errno = 0;
5040 //@TODO resolve env variable %WINDIR% first
5041 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5042 if (errno)
5044 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5047 #endif
5049 g_free(mailto);
5051 else
5053 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5058 * A menu which appear when right-clicking on buddy in contact list.
5060 static GList *
5061 sipe_buddy_menu(PurpleBuddy *buddy)
5063 PurpleBlistNode *g_node;
5064 PurpleGroup *group, *gr_parent;
5065 PurpleMenuAction *act;
5066 GList *menu = NULL;
5067 GList *menu_groups = NULL;
5069 act = purple_menu_action_new(_("Send Email..."),
5070 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5071 NULL, NULL);
5072 menu = g_list_prepend(menu, act);
5074 gr_parent = purple_buddy_get_group(buddy);
5075 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5076 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5077 continue;
5079 group = (PurpleGroup *)g_node;
5080 if (group == gr_parent)
5081 continue;
5083 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5084 continue;
5086 act = purple_menu_action_new(purple_group_get_name(group),
5087 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5088 group->name, NULL);
5089 menu_groups = g_list_prepend(menu_groups, act);
5091 menu_groups = g_list_reverse(menu_groups);
5093 act = purple_menu_action_new(_("Copy to"),
5094 NULL,
5095 NULL, menu_groups);
5096 menu = g_list_prepend(menu, act);
5097 menu = g_list_reverse(menu);
5099 return menu;
5102 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5103 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5104 return sipe_buddy_menu((PurpleBuddy *) node);
5105 } else {
5106 return NULL;
5110 static gboolean
5111 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5113 gboolean ret = TRUE;
5114 char *username = (char *)trans->payload;
5116 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5117 PurpleBuddy *pbuddy;
5118 struct sipe_buddy *sbuddy;
5119 const char *alias;
5120 char *server_alias = NULL;
5121 char *email = NULL;
5122 const char *device_name = NULL;
5124 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5126 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5127 alias = purple_buddy_get_local_alias(pbuddy);
5129 if (sip)
5131 //will query buddy UA's capabilities and send answer to log
5132 sipe_options_request(sip, username);
5134 sbuddy = g_hash_table_lookup(sip->buddies, username);
5135 if (sbuddy)
5137 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5141 if (msg->response != 200) {
5142 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5143 } else {
5144 xmlnode *searchResults;
5145 xmlnode *mrow;
5147 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5148 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5149 if (!searchResults) {
5150 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5151 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5152 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5153 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5154 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5155 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5156 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5157 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5158 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5159 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5160 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5161 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5162 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5163 if (!email || strcmp("", email)) {
5164 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5165 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5169 xmlnode_free(searchResults);
5172 purple_notify_user_info_add_section_break(info);
5174 if (!server_alias || !strcmp("", server_alias)) {
5175 g_free(server_alias);
5176 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5177 if (server_alias) {
5178 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5182 // same as server alias, do not present
5183 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5184 if (alias)
5186 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5189 if (!email || !strcmp("", email)) {
5190 g_free(email);
5191 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5192 if (email) {
5193 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5197 if (device_name)
5199 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5202 /* show a buddy's user info in a nice dialog box */
5203 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5204 username, /* buddy's username */
5205 info, /* body */
5206 NULL, /* callback called when dialog closed */
5207 NULL); /* userdata for callback */
5209 return ret;
5213 * AD search first, LDAP based
5215 static void sipe_get_info(PurpleConnection *gc, const char *username)
5217 char *row = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5218 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5220 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5221 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5222 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5223 g_free(body);
5224 g_free(row);
5227 static PurplePlugin *my_protocol = NULL;
5229 static PurplePluginProtocolInfo prpl_info =
5232 NULL, /* user_splits */
5233 NULL, /* protocol_options */
5234 NO_BUDDY_ICONS, /* icon_spec */
5235 sipe_list_icon, /* list_icon */
5236 NULL, /* list_emblems */
5237 sipe_status_text, /* status_text */
5238 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5239 sipe_status_types, /* away_states */
5240 sipe_blist_node_menu, /* blist_node_menu */
5241 NULL, /* chat_info */
5242 NULL, /* chat_info_defaults */
5243 sipe_login, /* login */
5244 sipe_close, /* close */
5245 sipe_im_send, /* send_im */
5246 NULL, /* set_info */ // TODO maybe
5247 sipe_send_typing, /* send_typing */
5248 sipe_get_info, /* get_info */
5249 sipe_set_status, /* set_status */
5250 NULL, /* set_idle */
5251 NULL, /* change_passwd */
5252 sipe_add_buddy, /* add_buddy */
5253 NULL, /* add_buddies */
5254 sipe_remove_buddy, /* remove_buddy */
5255 NULL, /* remove_buddies */
5256 sipe_add_permit, /* add_permit */
5257 sipe_add_deny, /* add_deny */
5258 sipe_add_deny, /* rem_permit */
5259 sipe_add_permit, /* rem_deny */
5260 dummy_permit_deny, /* set_permit_deny */
5261 NULL, /* join_chat */
5262 NULL, /* reject_chat */
5263 NULL, /* get_chat_name */
5264 NULL, /* chat_invite */
5265 NULL, /* chat_leave */
5266 NULL, /* chat_whisper */
5267 NULL, /* chat_send */
5268 sipe_keep_alive, /* keepalive */
5269 NULL, /* register_user */
5270 NULL, /* get_cb_info */ // deprecated
5271 NULL, /* get_cb_away */ // deprecated
5272 sipe_alias_buddy, /* alias_buddy */
5273 sipe_group_buddy, /* group_buddy */
5274 sipe_rename_group, /* rename_group */
5275 NULL, /* buddy_free */
5276 sipe_convo_closed, /* convo_closed */
5277 purple_normalize_nocase, /* normalize */
5278 NULL, /* set_buddy_icon */
5279 sipe_remove_group, /* remove_group */
5280 NULL, /* get_cb_real_name */ // TODO?
5281 NULL, /* set_chat_topic */
5282 NULL, /* find_blist_chat */
5283 NULL, /* roomlist_get_list */
5284 NULL, /* roomlist_cancel */
5285 NULL, /* roomlist_expand_category */
5286 NULL, /* can_receive_file */
5287 NULL, /* send_file */
5288 NULL, /* new_xfer */
5289 NULL, /* offline_message */
5290 NULL, /* whiteboard_prpl_ops */
5291 sipe_send_raw, /* send_raw */
5292 NULL, /* roomlist_room_serialize */
5293 NULL, /* unregister_user */
5294 NULL, /* send_attention */
5295 NULL, /* get_attention_types */
5297 sizeof(PurplePluginProtocolInfo), /* struct_size */
5298 sipe_get_account_text_table, /* get_account_text_table */
5302 static PurplePluginInfo info = {
5303 PURPLE_PLUGIN_MAGIC,
5304 PURPLE_MAJOR_VERSION,
5305 PURPLE_MINOR_VERSION,
5306 PURPLE_PLUGIN_PROTOCOL, /**< type */
5307 NULL, /**< ui_requirement */
5308 0, /**< flags */
5309 NULL, /**< dependencies */
5310 PURPLE_PRIORITY_DEFAULT, /**< priority */
5311 "prpl-sipe", /**< id */
5312 "Microsoft LCS/OCS", /**< name */
5313 VERSION, /**< version */
5314 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5315 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5316 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5317 "Gabriel Burt <gburt@novell.com>", /**< author */
5318 PURPLE_WEBSITE, /**< homepage */
5319 sipe_plugin_load, /**< load */
5320 sipe_plugin_unload, /**< unload */
5321 sipe_plugin_destroy, /**< destroy */
5322 NULL, /**< ui_info */
5323 &prpl_info, /**< extra_info */
5324 NULL,
5325 sipe_actions,
5326 NULL,
5327 NULL,
5328 NULL,
5329 NULL
5332 static void sipe_plugin_destroy(PurplePlugin *plugin)
5334 GList *entry;
5336 entry = prpl_info.protocol_options;
5337 while (entry) {
5338 purple_account_option_destroy(entry->data);
5339 entry = g_list_delete_link(entry, entry);
5341 prpl_info.protocol_options = NULL;
5343 entry = prpl_info.user_splits;
5344 while (entry) {
5345 purple_account_user_split_destroy(entry->data);
5346 entry = g_list_delete_link(entry, entry);
5348 prpl_info.user_splits = NULL;
5351 static void init_plugin(PurplePlugin *plugin)
5353 PurpleAccountUserSplit *split;
5354 PurpleAccountOption *option;
5356 #ifdef ENABLE_NLS
5357 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5358 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5359 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5360 #endif
5362 purple_plugin_register(plugin);
5364 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5365 purple_account_user_split_set_reverse(split, FALSE);
5366 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5368 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5369 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5370 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5371 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5373 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5374 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5375 // Translators: noun (networking port)
5376 option = purple_account_option_int_new(_("Port"), "port", 5061);
5377 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5379 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5380 purple_account_option_add_list_item(option, _("Auto"), "auto");
5381 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5382 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5383 purple_account_option_add_list_item(option, _("UDP"), "udp");
5384 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5386 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5387 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5389 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5390 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5392 // TODO commented out so won't show in the preferences until we fix krb message signing
5393 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5394 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5396 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5397 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5398 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5401 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5402 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5403 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5404 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5405 my_protocol = plugin;
5408 /* I had to redefined the function for it load, but works */
5409 gboolean purple_init_plugin(PurplePlugin *plugin){
5410 plugin->info = &(info);
5411 init_plugin((plugin));
5412 sipe_plugin_load((plugin));
5413 return purple_plugin_register(plugin);