Merge branch 'mob' of git+ssh://mob@repo.or.cz/srv/git/siplcs into mob
[siplcs.git] / src / sipe.c
blobd7530f7f1bb63eb6fd59df763e7135753ac11a33
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 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
134 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
135 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
136 gpointer data);
138 static void sipe_close(PurpleConnection *gc);
140 static void sipe_subscribe_to_name_single(struct sipe_account_data *sip, const char * buddy_name);
141 static void sipe_subscribe_to_name_batched(struct sipe_account_data *sip, const char * buddy_name);
142 static void send_presence_info(struct sipe_account_data *sip);
144 static void sendout_pkt(PurpleConnection *gc, const char *buf);
146 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
148 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
149 if (timeout != NULL) {
150 sscanf(timeout, "%u", &sip->keepalive_timeout);
151 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
152 sip->keepalive_timeout);
156 static void sipe_keep_alive(PurpleConnection *gc)
158 struct sipe_account_data *sip = gc->proto_data;
159 if (sip->transport == SIPE_TRANSPORT_UDP) {
160 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
161 gchar buf[2] = {0, 0};
162 purple_debug_info("sipe", "sending keep alive\n");
163 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
164 } else {
165 time_t now = time(NULL);
166 if ((sip->keepalive_timeout > 0) &&
167 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
168 #if PURPLE_VERSION_CHECK(2,4,0)
169 && ((now - gc->last_received) >= sip->keepalive_timeout)
170 #endif
172 purple_debug_info("sipe", "sending keep alive\n");
173 sendout_pkt(gc, "\r\n\r\n");
174 sip->last_keepalive = now;
179 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
181 struct sip_connection *ret = NULL;
182 GSList *entry = sip->openconns;
183 while (entry) {
184 ret = entry->data;
185 if (ret->fd == fd) return ret;
186 entry = entry->next;
188 return NULL;
191 static void sipe_auth_free(struct sip_auth *auth)
193 g_free(auth->nonce);
194 auth->nonce = NULL;
195 g_free(auth->opaque);
196 auth->opaque = NULL;
197 g_free(auth->realm);
198 auth->realm = NULL;
199 g_free(auth->target);
200 auth->target = NULL;
201 g_free(auth->digest_session_key);
202 auth->digest_session_key = NULL;
203 g_free(auth->ntlm_key);
204 auth->ntlm_key = NULL;
205 auth->type = AUTH_TYPE_UNSET;
206 auth->retries = 0;
207 auth->expires = 0;
210 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
212 struct sip_connection *ret = g_new0(struct sip_connection, 1);
213 ret->fd = fd;
214 sip->openconns = g_slist_append(sip->openconns, ret);
215 return ret;
218 static void connection_remove(struct sipe_account_data *sip, int fd)
220 struct sip_connection *conn = connection_find(sip, fd);
221 if (conn) {
222 sip->openconns = g_slist_remove(sip->openconns, conn);
223 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
224 g_free(conn->inbuf);
225 g_free(conn);
229 static void connection_free_all(struct sipe_account_data *sip)
231 struct sip_connection *ret = NULL;
232 GSList *entry = sip->openconns;
233 while (entry) {
234 ret = entry->data;
235 connection_remove(sip, ret->fd);
236 entry = sip->openconns;
240 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
242 const gchar *method = msg->method;
243 const gchar *target = msg->target;
244 gchar noncecount[9];
245 gchar *response;
246 gchar *ret;
247 gchar *tmp = NULL;
248 const char *authdomain;
249 const char *authuser;
250 //const char *krb5_realm;
251 const char *host;
252 //gchar *krb5_token = NULL;
254 authdomain = sip->authdomain;
255 authuser = sip->authuser;
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 (!authuser || strlen(authuser) < 1) {
284 authuser = sip->username;
287 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
288 sprintf(noncecount, "%08d", auth->nc++);
289 response = purple_cipher_http_digest_calculate_response(
290 "md5", method, target, NULL, NULL,
291 auth->nonce, noncecount, NULL, auth->digest_session_key);
292 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
294 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);
295 g_free(response);
296 return ret;
297 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
298 // If we have a signature for the message, include that
299 if (msg->signature) {
300 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);
301 return tmp;
304 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
305 const gchar *ntlm_key;
306 gchar *gssapi_data;
307 #if GLIB_CHECK_VERSION(2,8,0)
308 const gchar * hostname = g_get_host_name();
309 #else
310 static char hostname[256];
311 int ret = gethostname(hostname, sizeof(hostname));
312 hostname[sizeof(hostname) - 1] = '\0';
313 if (ret == -1 || hostname[0] == '\0') {
314 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
315 g_strerror(errno);
316 strcpy(hostname, "localhost");
318 #endif
319 /*const gchar * hostname = purple_get_host_name();*/
321 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
322 auth->ntlm_key = (gchar *)ntlm_key;
323 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
324 g_free(gssapi_data);
325 return tmp;
328 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
329 return tmp;
330 } else if (auth->type == AUTH_TYPE_KERBEROS) {
331 /* Kerberos */
332 if (auth->nc == 3) {
333 /*if (new_auth || force_reauth) {
334 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
335 if (auth->opaque) {
336 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);
337 } else {
338 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
340 } else {
341 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
342 gchar * mic = "MICTODO";
343 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
344 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
345 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
346 //auth->opaque ? auth->opaque : "", auth->target);
347 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
348 //g_free(mic);
350 return tmp;
352 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
355 sprintf(noncecount, "%08d", auth->nc++);
356 response = purple_cipher_http_digest_calculate_response(
357 "md5", method, target, NULL, NULL,
358 auth->nonce, noncecount, NULL, auth->digest_session_key);
359 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
361 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);
362 g_free(response);
363 return ret;
366 static char *parse_attribute(const char *attrname, const char *source)
368 const char *tmp, *tmp2;
369 char *retval = NULL;
370 int len = strlen(attrname);
372 if (!strncmp(source, attrname, len)) {
373 tmp = source + len;
374 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
375 if (tmp2)
376 retval = g_strndup(tmp, tmp2 - tmp);
377 else
378 retval = g_strdup(tmp);
381 return retval;
384 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
386 int i = 0;
387 const char *authuser;
388 char *tmp;
389 gchar **parts;
390 //const char *krb5_realm;
391 //const char *host;
393 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
394 // and do error checking
396 // KRB realm should always be uppercase
397 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
399 if (sip->realhostname) {
400 host = sip->realhostname;
401 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
402 host = purple_account_get_string(sip->account, "proxy", "");
403 } else {
404 host = sip->sipdomain;
407 authuser = sip->authuser;
409 if (!authuser || strlen(authuser) < 1) {
410 authuser = sip->username;
413 if (!hdr) {
414 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
415 return;
418 if (!g_strncasecmp(hdr, "NTLM", 4)) {
419 auth->type = AUTH_TYPE_NTLM;
420 parts = g_strsplit(hdr+5, "\", ", 0);
421 i = 0;
422 while (parts[i]) {
423 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
424 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
425 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
426 g_free(tmp);
428 if ((tmp = parse_attribute("targetname=\"",
429 parts[i]))) {
430 auth->target = tmp;
432 else if ((tmp = parse_attribute("realm=\"",
433 parts[i]))) {
434 auth->realm = tmp;
436 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
437 auth->opaque = tmp;
439 i++;
441 g_strfreev(parts);
442 auth->nc = 1;
443 if (!strstr(hdr, "gssapi-data")) {
444 auth->nc = 1;
445 } else {
446 auth->nc = 3;
448 return;
451 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
452 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
453 auth->type = AUTH_TYPE_KERBEROS;
454 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
455 parts = g_strsplit(hdr+9, "\", ", 0);
456 i = 0;
457 while (parts[i]) {
458 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
459 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
460 /*if (krb5_auth.gss_context == NULL) {
461 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
463 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
464 g_free(tmp);
466 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
467 auth->target = tmp;
468 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
469 auth->realm = tmp;
470 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
471 auth->opaque = tmp;
473 i++;
475 g_strfreev(parts);
476 auth->nc = 3;
477 return;
480 auth->type = AUTH_TYPE_DIGEST;
481 parts = g_strsplit(hdr, " ", 0);
482 while (parts[i]) {
483 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
484 auth->nonce = tmp;
486 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
487 auth->realm = tmp;
489 i++;
491 g_strfreev(parts);
493 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
494 if (auth->realm) {
495 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
496 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
498 auth->nc = 1;
502 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
504 PurpleConnection *gc = data;
505 struct sipe_account_data *sip = gc->proto_data;
506 gsize max_write;
507 gssize written;
509 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
511 if (max_write == 0) {
512 if (sip->tx_handler != 0){
513 purple_input_remove(sip->tx_handler);
514 sip->tx_handler = 0;
516 return;
519 written = write(sip->fd, sip->txbuf->outptr, max_write);
521 if (written < 0 && errno == EAGAIN)
522 written = 0;
523 else if (written <= 0) {
524 /*TODO: do we really want to disconnect on a failure to write?*/
525 purple_connection_error(gc, _("Could not write"));
526 return;
529 purple_circ_buffer_mark_read(sip->txbuf, written);
532 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
534 PurpleConnection *gc = data;
535 struct sipe_account_data *sip = gc->proto_data;
536 gsize max_write;
537 gssize written;
539 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
541 if (max_write == 0) {
542 if (sip->tx_handler != 0) {
543 purple_input_remove(sip->tx_handler);
544 sip->tx_handler = 0;
545 return;
549 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
551 if (written < 0 && errno == EAGAIN)
552 written = 0;
553 else if (written <= 0) {
554 /*TODO: do we really want to disconnect on a failure to write?*/
555 purple_connection_error(gc, _("Could not write"));
556 return;
559 purple_circ_buffer_mark_read(sip->txbuf, written);
562 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
564 static void send_later_cb(gpointer data, gint source, const gchar *error)
566 PurpleConnection *gc = data;
567 struct sipe_account_data *sip;
568 struct sip_connection *conn;
570 if (!PURPLE_CONNECTION_IS_VALID(gc))
572 if (source >= 0)
573 close(source);
574 return;
577 if (source < 0) {
578 purple_connection_error(gc, _("Could not connect"));
579 return;
582 sip = gc->proto_data;
583 sip->fd = source;
584 sip->connecting = FALSE;
585 sip->last_keepalive = time(NULL);
587 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
589 /* If there is more to write now, we need to register a handler */
590 if (sip->txbuf->bufused > 0)
591 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
593 conn = connection_create(sip, source);
594 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
597 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
599 struct sipe_account_data *sip;
600 struct sip_connection *conn;
602 if (!PURPLE_CONNECTION_IS_VALID(gc))
604 if (gsc) purple_ssl_close(gsc);
605 return NULL;
608 sip = gc->proto_data;
609 sip->fd = gsc->fd;
610 sip->gsc = gsc;
611 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
612 sip->connecting = FALSE;
613 sip->last_keepalive = time(NULL);
615 conn = connection_create(sip, gsc->fd);
617 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
619 return sip;
622 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
624 PurpleConnection *gc = data;
625 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
626 if (sip == NULL) return;
628 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
630 /* If there is more to write now */
631 if (sip->txbuf->bufused > 0) {
632 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
637 static void sendlater(PurpleConnection *gc, const char *buf)
639 struct sipe_account_data *sip = gc->proto_data;
641 if (!sip->connecting) {
642 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
643 if (sip->transport == SIPE_TRANSPORT_TLS){
644 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
645 } else {
646 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
647 purple_connection_error(gc, _("Couldn't create socket"));
650 sip->connecting = TRUE;
653 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
654 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
656 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
659 static void sendout_pkt(PurpleConnection *gc, const char *buf)
661 struct sipe_account_data *sip = gc->proto_data;
662 time_t currtime = time(NULL);
663 int writelen = strlen(buf);
665 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
666 if (sip->transport == SIPE_TRANSPORT_UDP) {
667 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
668 purple_debug_info("sipe", "could not send packet\n");
670 } else {
671 int ret;
672 if (sip->fd < 0) {
673 sendlater(gc, buf);
674 return;
677 if (sip->tx_handler) {
678 ret = -1;
679 errno = EAGAIN;
680 } else{
681 if (sip->gsc){
682 ret = purple_ssl_write(sip->gsc, buf, writelen);
683 }else{
684 ret = write(sip->fd, buf, writelen);
688 if (ret < 0 && errno == EAGAIN)
689 ret = 0;
690 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
691 sendlater(gc, buf);
692 return;
695 if (ret < writelen) {
696 if (!sip->tx_handler){
697 if (sip->gsc){
698 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
700 else{
701 sip->tx_handler = purple_input_add(sip->fd,
702 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
703 gc);
707 /* XXX: is it OK to do this? You might get part of a request sent
708 with part of another. */
709 if (sip->txbuf->bufused > 0)
710 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
712 purple_circ_buffer_append(sip->txbuf, buf + ret,
713 writelen - ret);
718 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
720 sendout_pkt(gc, buf);
721 return len;
724 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
726 GSList *tmp = msg->headers;
727 gchar *name;
728 gchar *value;
729 GString *outstr = g_string_new("");
730 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
731 while (tmp) {
732 name = ((struct siphdrelement*) (tmp->data))->name;
733 value = ((struct siphdrelement*) (tmp->data))->value;
734 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
735 tmp = g_slist_next(tmp);
737 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
738 sendout_pkt(sip->gc, outstr->str);
739 g_string_free(outstr, TRUE);
742 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
744 gchar * buf;
745 if (sip->registrar.ntlm_key) {
746 struct sipmsg_breakdown msgbd;
747 gchar *signature_input_str;
748 msgbd.msg = msg;
749 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
750 msgbd.rand = g_strdup_printf("%08x", g_random_int());
751 sip->registrar.ntlm_num++;
752 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
753 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
754 if (signature_input_str != NULL) {
755 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
756 msg->rand = g_strdup(msgbd.rand);
757 msg->num = g_strdup(msgbd.num);
758 g_free(signature_input_str);
760 sipmsg_breakdown_free(&msgbd);
763 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
764 buf = auth_header(sip, &sip->registrar, msg);
765 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
766 sipmsg_add_header(msg, "Authorization", buf);
767 } else {
768 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
770 g_free(buf);
771 } 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")) {
772 sip->registrar.nc = 3;
773 sip->registrar.type = AUTH_TYPE_NTLM;
775 buf = auth_header(sip, &sip->registrar, msg);
776 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
777 g_free(buf);
778 } else {
779 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
783 static char *get_contact(struct sipe_account_data *sip)
785 return g_strdup(sip->contact);
789 * unused. Needed?
790 static char *get_contact_service(struct sipe_account_data *sip)
792 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()));
793 //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);
797 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
798 const char *text, const char *body)
800 gchar *name;
801 gchar *value;
802 GString *outstr = g_string_new("");
803 struct sipe_account_data *sip = gc->proto_data;
804 gchar *contact;
805 GSList *tmp;
807 sipmsg_remove_header(msg, "ms-user-data");
809 contact = get_contact(sip);
810 sipmsg_remove_header(msg, "Contact");
811 sipmsg_add_header(msg, "Contact", contact);
812 g_free(contact);
814 /* When sending the acknowlegements and errors, the content length from the original
815 message is still here, but there is no body; we need to make sure we're sending the
816 correct content length */
817 sipmsg_remove_header(msg, "Content-Length");
818 if (body) {
819 gchar len[12];
820 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
821 sipmsg_add_header(msg, "Content-Length", len);
822 } else {
823 sipmsg_remove_header(msg, "Content-Type");
824 sipmsg_add_header(msg, "Content-Length", "0");
827 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
828 //gchar * mic = "MICTODO";
829 msg->response = code;
831 sipmsg_remove_header(msg, "Authentication-Info");
832 sign_outgoing_message(msg, sip, msg->method);
834 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
835 tmp = msg->headers;
836 while (tmp) {
837 name = ((struct siphdrelement*) (tmp->data))->name;
838 value = ((struct siphdrelement*) (tmp->data))->value;
840 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
841 tmp = g_slist_next(tmp);
843 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
844 sendout_pkt(gc, outstr->str);
845 g_string_free(outstr, TRUE);
848 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
850 if (trans->msg) sipmsg_free(trans->msg);
851 sip->transactions = g_slist_remove(sip->transactions, trans);
852 g_free(trans);
855 static struct transaction *
856 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
858 struct transaction *trans = g_new0(struct transaction, 1);
859 trans->time = time(NULL);
860 trans->msg = (struct sipmsg *)msg;
861 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
862 trans->callback = callback;
863 sip->transactions = g_slist_append(sip->transactions, trans);
864 return trans;
867 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
869 struct transaction *trans;
870 GSList *transactions = sip->transactions;
871 gchar *cseq = sipmsg_find_header(msg, "CSeq");
873 while (transactions) {
874 trans = transactions->data;
875 if (!strcmp(trans->cseq, cseq)) {
876 return trans;
878 transactions = transactions->next;
881 return NULL;
884 static struct transaction *
885 send_sip_request(PurpleConnection *gc, const gchar *method,
886 const gchar *url, const gchar *to, const gchar *addheaders,
887 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
889 struct sipe_account_data *sip = gc->proto_data;
890 const char *addh = "";
891 char *buf;
892 struct sipmsg *msg;
893 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
894 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
895 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
896 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
897 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
898 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
899 gchar *route = strdup("");
900 gchar *epid = get_epid(); // TODO generate one per account/login
901 struct transaction *trans;
903 if (dialog && dialog->routes)
905 GSList *iter = dialog->routes;
907 while(iter)
909 char *tmp = route;
910 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
911 g_free(tmp);
912 iter = g_slist_next(iter);
916 if (!ourtag && !dialog) {
917 ourtag = gentag();
920 if (!strcmp(method, "REGISTER")) {
921 if (sip->regcallid) {
922 g_free(callid);
923 callid = g_strdup(sip->regcallid);
924 } else {
925 sip->regcallid = g_strdup(callid);
929 if (addheaders) addh = addheaders;
931 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
932 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
933 "From: <sip:%s>%s%s;epid=%s\r\n"
934 "To: <%s>%s%s%s%s\r\n"
935 "Max-Forwards: 70\r\n"
936 "CSeq: %d %s\r\n"
937 "User-Agent: %s\r\n"
938 "Call-ID: %s\r\n"
939 "%s%s"
940 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
941 method,
942 dialog && dialog->request ? dialog->request : url,
943 TRANSPORT_DESCRIPTOR,
944 purple_network_get_my_ip(-1),
945 sip->listenport,
946 branch ? ";branch=" : "",
947 branch ? branch : "",
948 sip->username,
949 ourtag ? ";tag=" : "",
950 ourtag ? ourtag : "",
951 epid,
953 theirtag ? ";tag=" : "",
954 theirtag ? theirtag : "",
955 theirepid ? ";epid=" : "",
956 theirepid ? theirepid : "",
957 dialog ? ++dialog->cseq : ++sip->cseq,
958 method,
959 useragent,
960 callid,
961 route,
962 addh,
963 body ? strlen(body) : 0,
964 body ? body : "");
967 //printf ("parsing msg buf:\n%s\n\n", buf);
968 msg = sipmsg_parse_msg(buf);
970 g_free(buf);
971 g_free(ourtag);
972 g_free(theirtag);
973 g_free(theirepid);
974 g_free(branch);
975 g_free(callid);
976 g_free(route);
977 g_free(epid);
979 sign_outgoing_message (msg, sip, method);
981 buf = sipmsg_to_string (msg);
983 /* add to ongoing transactions */
984 trans = transactions_add_buf(sip, msg, tc);
985 sendout_pkt(gc, buf);
986 g_free(buf);
988 return trans;
991 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
993 gchar *from = g_strdup_printf("sip:%s", sip->username);
994 gchar *contact = get_contact(sip);
995 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
996 "Content-Type: application/SOAP+xml\r\n",contact);
998 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
999 tr->payload = payload;
1001 g_free(from);
1002 g_free(hdr);
1005 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1007 send_soap_request_with_cb(sip, body, NULL, NULL);
1010 static char *get_contact_register(struct sipe_account_data *sip)
1012 char *epid = get_epid();
1013 char *uuid = generateUUIDfromEPID(epid);
1014 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1015 g_free(uuid);
1016 g_free(epid);
1017 return(buf);
1020 static void do_register_exp(struct sipe_account_data *sip, int expire)
1022 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1023 char *to = g_strdup_printf("sip:%s", sip->username);
1024 char *contact = get_contact_register(sip);
1025 char *hdr = g_strdup_printf("Contact: %s\r\n"
1026 "Supported: gruu-10, adhoclist, msrtc-event-categories\r\n"
1027 "Event: registration\r\n"
1028 "Allow-Events: presence\r\n"
1029 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1030 "Expires: %d\r\n", contact,expire);
1031 g_free(contact);
1033 sip->registerstatus = 1;
1035 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1036 process_register_response);
1038 g_free(hdr);
1039 g_free(uri);
1040 g_free(to);
1043 static void do_register_cb(struct sipe_account_data *sip)
1045 do_register_exp(sip, sip->registerexpire);
1046 sip->reregister_set = FALSE;
1049 static void do_register(struct sipe_account_data *sip)
1051 do_register_exp(sip, sip->registerexpire);
1055 * Returns URI from provided To or From header.
1057 * Needs to g_free() after use.
1059 * @return URI with sip: prefix
1061 static gchar *parse_from(const gchar *hdr)
1063 gchar *from;
1064 const gchar *tmp, *tmp2 = hdr;
1066 if (!hdr) return NULL;
1067 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1068 tmp = strchr(hdr, '<');
1070 /* i hate the different SIP UA behaviours... */
1071 if (tmp) { /* sip address in <...> */
1072 tmp2 = tmp + 1;
1073 tmp = strchr(tmp2, '>');
1074 if (tmp) {
1075 from = g_strndup(tmp2, tmp - tmp2);
1076 } else {
1077 purple_debug_info("sipe", "found < without > in From\n");
1078 return NULL;
1080 } else {
1081 tmp = strchr(tmp2, ';');
1082 if (tmp) {
1083 from = g_strndup(tmp2, tmp - tmp2);
1084 } else {
1085 from = g_strdup(tmp2);
1088 purple_debug_info("sipe", "got %s\n", from);
1089 return from;
1092 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1094 va_list args;
1095 xmlnode * node = NULL;
1096 const gchar * name;
1098 va_start(args, parent);
1099 while ((name = va_arg(args, const char *)) != NULL) {
1100 node = xmlnode_get_child(parent, name);
1101 if (node == NULL) return NULL;
1102 parent = node;
1104 va_end(args);
1106 return node;
1110 static void
1111 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1113 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1114 send_soap_request(sip, body);
1115 g_free(body);
1118 static void
1119 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1121 if (allow) {
1122 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1123 } else {
1124 purple_debug_info("sipe", "Blocking contact %s\n", who);
1127 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1130 static
1131 void sipe_auth_user_cb(void * data)
1133 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1134 if (!job) return;
1136 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1137 g_free(job);
1140 static
1141 void sipe_deny_user_cb(void * data)
1143 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1144 if (!job) return;
1146 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1147 g_free(job);
1150 static void
1151 sipe_add_permit(PurpleConnection *gc, const char *name)
1153 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1154 sipe_contact_allow_deny(sip, name, TRUE);
1157 static void
1158 sipe_add_deny(PurpleConnection *gc, const char *name)
1160 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1161 sipe_contact_allow_deny(sip, name, FALSE);
1164 /*static void
1165 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1167 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1168 sipe_contact_set_acl(sip, name, "");
1171 static void
1172 sipe_process_incoming_pending (struct sipe_account_data *sip, struct sipmsg * msg)
1174 xmlnode *watchers;
1175 xmlnode *watcher;
1176 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1177 if (msg->response != 0 && msg->response != 200) return;
1179 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1181 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1182 if (!watchers) return;
1184 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1185 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1186 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1187 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1189 // TODO pull out optional displayName to pass as alias
1190 if (remote_user) {
1191 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1192 job->who = remote_user;
1193 job->sip = sip;
1194 purple_account_request_authorization(
1195 sip->account,
1196 remote_user,
1197 NULL, // id
1198 alias,
1199 NULL, // message
1200 on_list,
1201 sipe_auth_user_cb,
1202 sipe_deny_user_cb,
1203 (void *) job);
1208 xmlnode_free(watchers);
1209 return;
1212 static void
1213 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1215 PurpleGroup * purple_group = purple_find_group(group->name);
1216 if (!purple_group) {
1217 purple_group = purple_group_new(group->name);
1218 purple_blist_add_group(purple_group, NULL);
1221 if (purple_group) {
1222 group->purple_group = purple_group;
1223 sip->groups = g_slist_append(sip->groups, group);
1224 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1225 } else {
1226 purple_debug_info("sipe", "did not add group %s\n", group->name);
1230 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1232 struct sipe_group *group;
1233 GSList *entry;
1234 if (sip == NULL) {
1235 return NULL;
1238 entry = sip->groups;
1239 while (entry) {
1240 group = entry->data;
1241 if (group->id == id) {
1242 return group;
1244 entry = entry->next;
1246 return NULL;
1249 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1251 struct sipe_group *group;
1252 GSList *entry;
1253 if (sip == NULL) {
1254 return NULL;
1257 entry = sip->groups;
1258 while (entry) {
1259 group = entry->data;
1260 if (!strcmp(group->name, name)) {
1261 return group;
1263 entry = entry->next;
1265 return NULL;
1268 static void
1269 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1271 gchar *body;
1272 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1273 body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1274 send_soap_request(sip, body);
1275 g_free(body);
1276 g_free(group->name);
1277 group->name = g_strdup(name);
1281 * Only appends if no such value already stored.
1282 * Like Set in Java.
1284 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1285 GSList * res = list;
1286 if (!g_slist_find_custom(list, data, func)) {
1287 res = g_slist_insert_sorted(list, data, func);
1289 return res;
1292 static int
1293 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1294 return group1->id - group2->id;
1298 * Returns string like "2 4 7 8" - group ids buddy belong to.
1300 static gchar *
1301 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1302 int i = 0;
1303 gchar *res;
1304 //creating array from GList, converting int to gchar*
1305 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1306 GSList *entry = buddy->groups;
1307 while (entry) {
1308 struct sipe_group * group = entry->data;
1309 ids_arr[i] = g_strdup_printf("%d", group->id);
1310 entry = entry->next;
1311 i++;
1313 ids_arr[i] = NULL;
1314 res = g_strjoinv(" ", ids_arr);
1315 g_strfreev(ids_arr);
1316 return res;
1320 * Sends buddy update to server
1322 static void
1323 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1325 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1326 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1328 if (buddy && purple_buddy) {
1329 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1330 gchar *body;
1331 gchar *groups = sipe_get_buddy_groups_string(buddy);
1332 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1334 body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1335 alias, groups, "true", buddy->name, sip->contacts_delta++
1337 send_soap_request(sip, body);
1338 g_free(groups);
1339 g_free(body);
1343 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1345 if (msg->response == 200) {
1346 struct sipe_group * group = g_new0(struct sipe_group, 1);
1348 struct group_user_context * ctx = (struct group_user_context*)tc->payload;
1349 xmlnode *xml;
1350 xmlnode *node;
1351 char *group_id;
1352 struct sipe_buddy *buddy;
1353 group->name = ctx->group_name;
1355 xml = xmlnode_from_str(msg->body, msg->bodylen);
1356 if (!xml) return FALSE;
1358 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1359 if (!node) return FALSE;
1361 group_id = xmlnode_get_data(node);
1362 if (!group_id) return FALSE;
1364 group->id = (int)g_ascii_strtod(group_id, NULL);
1366 sipe_group_add(sip, group);
1368 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1369 if (buddy) {
1370 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1373 sipe_group_set_user(sip, ctx->user_name);
1375 g_free(ctx);
1376 xmlnode_free(xml);
1377 return TRUE;
1379 return FALSE;
1382 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1384 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1385 gchar *body;
1386 ctx->group_name = g_strdup(name);
1387 ctx->user_name = g_strdup(who);
1389 body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1390 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1391 g_free(body);
1395 * A timer callback
1396 * Should return FALSE if repetitive action is not needed
1398 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1400 gboolean ret;
1401 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1402 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1403 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1404 (sched_action->action)(sched_action->sip, sched_action->payload);
1405 ret = sched_action->repetitive;
1406 g_free(sched_action->payload);
1407 g_free(sched_action->name);
1408 g_free(sched_action);
1409 return ret;
1413 * Do schedule action for execution in the future.
1414 * Non repetitive execution.
1416 * @param name of action (will be copied)
1417 * @param timeout in seconds
1418 * @action callback function
1419 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1421 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1423 struct scheduled_action *sched_action;
1425 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1426 sched_action = g_new0(struct scheduled_action, 1);
1427 sched_action->repetitive = FALSE;
1428 sched_action->name = g_strdup(name);
1429 sched_action->action = action;
1430 sched_action->sip = sip;
1431 sched_action->payload = payload;
1432 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1433 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1434 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1438 * Kills action timer effectively cancelling
1439 * scheduled action
1441 * @param name of action
1443 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1445 GSList *entry;
1447 if (!sip->timeouts || !name) return;
1449 entry = sip->timeouts;
1450 while (entry) {
1451 struct scheduled_action *sched_action = entry->data;
1452 if(!strcmp(sched_action->name, name)) {
1453 GSList *to_delete = entry;
1454 entry = entry->next;
1455 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1456 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1457 purple_timeout_remove(sched_action->timeout_handler);
1458 g_free(sched_action->payload);
1459 g_free(sched_action->name);
1460 g_free(sched_action);
1461 } else {
1462 entry = entry->next;
1467 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1469 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1471 gchar *to;
1472 //purple_debug_info("sipe","process_subscribe_response: body:\n%s\n", msg->body);
1474 if (msg->response == 200 || msg->response == 202)
1476 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1478 process_incoming_notify(sip, msg, FALSE, FALSE);
1480 return TRUE;
1483 /* we can not subscribe -> user is offline (TODO unknown status?) */
1484 to = parse_from(sipmsg_find_header(tc->msg, "To")); /* can't be NULL since it is our own msg */
1485 purple_prpl_got_user_status(sip->account, to, "offline", NULL);
1486 g_free(to);
1487 return TRUE;
1492 * Batch Category SUBSCRIBE [SIP-PRES] - msrtc-event-categories+xml
1493 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list
1494 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1498 static void sipe_subscribe_to_name_batched(struct sipe_account_data *sip, const char * buddy_name){
1499 gchar *resource_uri = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1500 gchar *to = g_strdup_printf("sip:%s", sip->username);
1501 gchar *tmp = get_contact(sip);
1502 gchar *request;
1503 gchar *content;
1504 request = g_strdup_printf(
1505 "Require: adhoclist, categoryList\r\n"
1506 "Supported: eventlist\r\n"
1507 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1508 "Supported: ms-piggyback-first-notify\r\n"
1509 "Supported: com.microsoft.autoextend\r\n"
1510 "Supported: ms-benotify\r\n"
1511 "Proxy-Require: ms-benotify\r\n"
1512 "Event: presence\r\n"
1513 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1514 "Contact: %s\r\n", tmp);
1516 content = g_strdup_printf(
1517 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1518 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1519 "<resource uri=\"%s\"/>\n"
1520 "</adhocList>\n"
1521 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1522 "<category name=\"note\"/>\n"
1523 "<category name=\"state\"/>\n"
1524 "</categoryList>\n"
1525 "</action>\n"
1526 "</batchSub>", sip->username, resource_uri
1529 g_free(tmp);
1531 /* subscribe to buddy presence */
1532 send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1534 g_free(content);
1535 g_free(to);
1536 g_free(request);
1540 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1541 * The user sends a single SUBSCRIBE request to the subscribed contact.
1542 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1546 static void sipe_subscribe_to_name_single(struct sipe_account_data *sip, const char * buddy_name)
1548 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1549 gchar *tmp = get_contact(sip);
1550 gchar *request;
1551 gchar *content;
1552 request = g_strdup_printf(
1553 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1554 "Supported: ms-piggyback-first-notify\r\n"
1555 "Supported: com.microsoft.autoextend\r\n"
1556 "Supported: ms-benotify\r\n"
1557 "Proxy-Require: ms-benotify\r\n"
1558 "Event: presence\r\n"
1559 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1560 "Contact: %s\r\n", tmp);
1562 content = g_strdup_printf(
1563 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1564 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1565 "<resource uri=\"%s\"/>\n"
1566 "</adhocList>\n"
1567 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1568 "<category name=\"note\"/>\n"
1569 "<category name=\"state\"/>\n"
1570 "</categoryList>\n"
1571 "</action>\n"
1572 "</batchSub>", sip->username, to
1575 g_free(tmp);
1577 /* subscribe to buddy presence */
1578 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1580 g_free(content);
1581 g_free(to);
1582 g_free(request);
1585 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1587 const char *status_id = purple_status_get_id(status);
1588 struct sipe_account_data *sip = NULL;
1590 if (!purple_status_is_active(status))
1591 return;
1593 if (account->gc)
1594 sip = account->gc->proto_data;
1596 if (sip) {
1597 g_free(sip->status);
1598 sip->status = g_strdup(status_id);
1599 send_presence_info(sip);
1603 static void
1604 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1606 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1607 sipe_group_set_user(sip, name);
1610 static void
1611 sipe_group_buddy(PurpleConnection *gc,
1612 const char *who,
1613 const char *old_group_name,
1614 const char *new_group_name)
1616 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1617 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1618 struct sipe_group * old_group = NULL;
1619 struct sipe_group * new_group;
1621 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1622 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1624 if(!buddy) { // buddy not in roaming list
1625 return;
1628 if (old_group_name) {
1629 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1631 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1633 if (old_group) {
1634 buddy->groups = g_slist_remove(buddy->groups, old_group);
1635 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1638 if (!new_group) {
1639 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1640 } else {
1641 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1642 sipe_group_set_user(sip, who);
1646 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1648 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1649 struct sipe_buddy *b;
1651 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1653 // Prepend sip: if needed
1654 if (strncmp("sip:", buddy->name, 4)) {
1655 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1656 purple_blist_rename_buddy(buddy, buf);
1657 g_free(buf);
1660 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1661 b = g_new0(struct sipe_buddy, 1);
1662 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1663 b->name = g_strdup(buddy->name);
1664 g_hash_table_insert(sip->buddies, b->name, b);
1665 sipe_group_buddy(gc, b->name, NULL, group->name);
1666 sipe_subscribe_to_name_batched(sip, b->name); //@TODO should go to callback
1667 } else {
1668 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1673 * Unassociates buddy from group first.
1674 * Then see if no groups left, removes buddy completely.
1675 * Otherwise updates buddy groups on server.
1677 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1679 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1680 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1681 struct sipe_group *g = NULL;
1683 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1685 if (!b) return;
1687 if (group) {
1688 g = sipe_group_find_by_name(sip, group->name);
1691 if (g) {
1692 b->groups = g_slist_remove(b->groups, g);
1693 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1696 if (g_slist_length(b->groups) < 1) {
1697 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1698 sipe_cancel_scheduled_action(sip, action_name);
1699 g_free(action_name);
1701 g_hash_table_remove(sip->buddies, buddy->name);
1703 if (b->name) {
1704 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1705 send_soap_request(sip, body);
1706 g_free(body);
1709 g_free(b->name);
1710 g_free(b->annotation);
1711 g_free(b->device_name);
1712 g_slist_free(b->groups);
1713 g_free(b);
1714 } else {
1715 //updates groups on server
1716 sipe_group_set_user(sip, b->name);
1721 static void
1722 sipe_rename_group(PurpleConnection *gc,
1723 const char *old_name,
1724 PurpleGroup *group,
1725 GList *moved_buddies)
1727 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1728 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1729 if (group) {
1730 sipe_group_rename(sip, s_group, group->name);
1731 } else {
1732 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1736 static void
1737 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1739 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1740 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1741 if (s_group) {
1742 gchar *body;
1743 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1744 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1745 send_soap_request(sip, body);
1746 g_free(body);
1748 sip->groups = g_slist_remove(sip->groups, s_group);
1749 g_free(s_group->name);
1750 } else {
1751 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1755 static GList *sipe_status_types(PurpleAccount *acc)
1757 PurpleStatusType *type;
1758 GList *types = NULL;
1760 // Online
1761 type = purple_status_type_new_with_attrs(
1762 PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE,
1763 // Translators: noun
1764 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1765 NULL);
1766 types = g_list_append(types, type);
1768 // Busy
1769 type = purple_status_type_new_with_attrs(
1770 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1771 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1772 NULL);
1773 types = g_list_append(types, type);
1775 // Do Not Disturb (Not let user set it)
1776 type = purple_status_type_new_with_attrs(
1777 PURPLE_STATUS_UNAVAILABLE, "do-not-disturb", "Do Not Disturb", TRUE, FALSE, FALSE,
1778 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1779 NULL);
1780 types = g_list_append(types, type);
1782 // Be Right Back
1783 type = purple_status_type_new_with_attrs(
1784 PURPLE_STATUS_AWAY, "be-right-back", _("Be Right Back"), TRUE, TRUE, FALSE,
1785 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1786 NULL);
1787 types = g_list_append(types, type);
1789 // Away
1790 type = purple_status_type_new_with_attrs(
1791 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1792 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1793 NULL);
1794 types = g_list_append(types, type);
1796 //On The Phone
1797 type = purple_status_type_new_with_attrs(
1798 PURPLE_STATUS_UNAVAILABLE, "on-the-phone", _("On The Phone"), TRUE, TRUE, FALSE,
1799 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1800 NULL);
1801 types = g_list_append(types, type);
1803 //Out To Lunch
1804 type = purple_status_type_new_with_attrs(
1805 PURPLE_STATUS_AWAY, "out-to-lunch", "Out To Lunch", TRUE, TRUE, FALSE,
1806 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1807 NULL);
1808 types = g_list_append(types, type);
1810 //Appear Offline
1811 type = purple_status_type_new_full(
1812 PURPLE_STATUS_INVISIBLE, NULL, "Appear Offline", TRUE, TRUE, FALSE);
1813 types = g_list_append(types, type);
1815 // Offline
1816 type = purple_status_type_new_full(
1817 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1818 types = g_list_append(types, type);
1820 return types;
1824 * A callback for g_hash_table_foreach
1826 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1828 sipe_subscribe_to_name_batched(sip, buddy->name);
1832 * Removes entries from purple buddy list
1833 * that does not correspond ones in the roaming contact list.
1835 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1836 GSList *buddies = purple_find_buddies(sip->account, NULL);
1837 GSList *entry = buddies;
1838 struct sipe_buddy *buddy;
1839 PurpleBuddy *b;
1840 PurpleGroup *g;
1842 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1843 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1844 while (entry) {
1845 b = entry->data;
1846 g = purple_buddy_get_group(b);
1847 buddy = g_hash_table_lookup(sip->buddies, b->name);
1848 if(buddy) {
1849 gboolean in_sipe_groups = FALSE;
1850 GSList *entry2 = buddy->groups;
1851 while (entry2) {
1852 struct sipe_group *group = entry2->data;
1853 if (!strcmp(group->name, g->name)) {
1854 in_sipe_groups = TRUE;
1855 break;
1857 entry2 = entry2->next;
1859 if(!in_sipe_groups) {
1860 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1861 purple_blist_remove_buddy(b);
1863 } else {
1864 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1865 purple_blist_remove_buddy(b);
1867 entry = entry->next;
1871 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1873 int len = msg->bodylen;
1875 gchar *tmp = sipmsg_find_header(msg, "Event");
1876 xmlnode *item;
1877 xmlnode *isc;
1878 gchar *contacts_delta;
1879 xmlnode *group_node;
1880 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1881 return FALSE;
1884 purple_debug_info("sipe", "msg->body:%s\n", msg->body);
1886 /* Convert the contact from XML to Purple Buddies */
1887 isc = xmlnode_from_str(msg->body, len);
1888 if (!isc) {
1889 return FALSE;
1892 contacts_delta = g_strdup(xmlnode_get_attrib(isc, "deltaNum"));
1893 if (contacts_delta) {
1894 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1897 /* Parse groups */
1898 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1899 struct sipe_group * group = g_new0(struct sipe_group, 1);
1901 group->name = g_strdup(xmlnode_get_attrib(group_node, "name"));
1902 if (!strncmp(group->name, "~", 1)){
1903 // TODO translate
1904 group->name = "Other Contacts";
1906 group->name = g_strdup(group->name);
1907 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1909 sipe_group_add(sip, group);
1912 // Make sure we have at least one group
1913 if (g_slist_length(sip->groups) == 0) {
1914 struct sipe_group * group = g_new0(struct sipe_group, 1);
1915 PurpleGroup *purple_group;
1916 // TODO translate
1917 group->name = g_strdup("Other Contacts");
1918 group->id = 1;
1919 purple_group = purple_group_new(group->name);
1920 purple_blist_add_group(purple_group, NULL);
1921 sip->groups = g_slist_append(sip->groups, group);
1924 /* Parse contacts */
1925 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1926 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1927 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1928 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1929 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1930 gchar **item_groups;
1931 struct sipe_group *group = NULL;
1932 struct sipe_buddy *buddy = NULL;
1933 int i = 0;
1935 // assign to group Other Contacts if nothing else received
1936 if(!groups || !strcmp("", groups) ) {
1937 group = sipe_group_find_by_name(sip, "Other Contacts");
1938 groups = group ? g_strdup_printf("%d", group->id) : "1";
1941 item_groups = g_strsplit(groups, " ", 0);
1943 while (item_groups[i]) {
1944 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1946 // If couldn't find the right group for this contact, just put them in the first group we have
1947 if (group == NULL && g_slist_length(sip->groups) > 0) {
1948 group = sip->groups->data;
1951 if (group != NULL) {
1952 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1953 if (!b){
1954 b = purple_buddy_new(sip->account, buddy_name, uri);
1955 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1958 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1959 if (name != NULL && strlen(name) != 0) {
1960 purple_blist_alias_buddy(b, name);
1964 if (!buddy) {
1965 buddy = g_new0(struct sipe_buddy, 1);
1966 buddy->name = g_strdup(b->name);
1967 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1970 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1972 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
1973 } else {
1974 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1975 name);
1978 i++;
1979 } // while, contact groups
1980 g_strfreev(item_groups);
1981 g_free(groups);
1982 g_free(name);
1983 g_free(buddy_name);
1984 g_free(uri);
1986 } // for, contacts
1988 xmlnode_free(isc);
1990 sipe_cleanup_local_blist(sip);
1992 //subscribe to buddies
1993 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1994 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
1995 sip->subscribed_buddies = TRUE;
1998 return 0;
2004 * Subscribe roaming contacts
2006 static void sipe_subscribe_buddylist(struct sipe_account_data *sip,struct sipmsg *msg)
2008 gchar *to = g_strdup_printf("sip:%s", sip->username);
2009 gchar *tmp = get_contact(sip);
2010 gchar *hdr = g_strdup_printf(
2011 "Event: vnd-microsoft-roaming-contacts\r\n"
2012 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2013 "Supported: com.microsoft.autoextend\r\n"
2014 "Supported: ms-benotify\r\n"
2015 "Proxy-Require: ms-benotify\r\n"
2016 "Supported: ms-piggyback-first-notify\r\n"
2017 "Contact: %s\r\n", tmp);
2018 g_free(tmp);
2020 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_roaming_contacts);
2021 g_free(to);
2022 g_free(hdr);
2025 static gboolean
2026 sipe_process_pending_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2028 sipe_process_incoming_pending (sip, msg);
2029 return TRUE;
2032 static void sipe_subscribe_pending_buddies(struct sipe_account_data *sip,struct sipmsg *msg)
2034 gchar *to = g_strdup_printf("sip:%s", sip->username);
2035 gchar *tmp = get_contact(sip);
2036 gchar *hdr = g_strdup_printf(
2037 "Event: presence.wpending\r\n"
2038 "Accept: text/xml+msrtc.wpending\r\n"
2039 "Supported: com.microsoft.autoextend\r\n"
2040 "Supported: ms-benotify\r\n"
2041 "Proxy-Require: ms-benotify\r\n"
2042 "Supported: ms-piggyback-first-notify\r\n"
2043 "Contact: %s\r\n", tmp);
2044 g_free(tmp);
2046 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_pending_response);
2047 g_free(to);
2048 g_free(hdr);
2051 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2053 const gchar *contacts_delta;
2054 xmlnode *xml;
2056 xml = xmlnode_from_str(msg->body, msg->bodylen);
2057 if (!xml)
2059 return;
2062 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2063 if (contacts_delta)
2065 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2068 xmlnode_free(xml);
2071 static gboolean
2072 sipe_process_acl_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2074 sipe_process_roaming_acl(sip, msg);
2075 return TRUE;
2079 * When we receive some self (BE) NOTIFY with a new subscriber
2080 * we sends a setSubscribers request to him [SIP-PRES]
2084 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2086 gchar *to = g_strdup_printf("sip:%s", sip->username);
2087 gchar *tmp = get_contact(sip);
2088 xmlnode *xml;
2089 xmlnode *node;
2090 const char * user;
2092 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2094 xml = xmlnode_from_str(msg->body, msg->bodylen);
2095 if (!xml) return;
2097 node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL);
2098 if (!node) return;
2100 user = xmlnode_get_attrib(node, "user");
2101 if (!user) return;
2103 gchar *hdr = g_strdup_printf(
2104 "Contact: %s\r\n"
2105 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", tmp);
2107 gchar *body=g_strdup_printf(
2108 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2109 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2110 "</setSubscribers>",user);
2112 g_free(tmp);
2113 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2114 g_free(body);
2115 g_free(to);
2116 g_free(hdr);
2119 static void sipe_subscribe_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2121 gchar *to = g_strdup_printf("sip:%s", sip->username);
2122 gchar *tmp = get_contact(sip);
2123 gchar *hdr = g_strdup_printf(
2124 "Event: vnd-microsoft-roaming-ACL\r\n"
2125 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2126 "Supported: com.microsoft.autoextend\r\n"
2127 "Supported: ms-benotify\r\n"
2128 "Proxy-Require: ms-benotify\r\n"
2129 "Supported: ms-piggyback-first-notify\r\n"
2130 "Contact: %s\r\n", tmp);
2131 g_free(tmp);
2133 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_acl_response);
2134 g_free(to);
2135 g_free(hdr);
2139 * To request for presence information about the user, access level settings that have already been configured by the user
2140 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2141 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2144 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2146 gchar *to = g_strdup_printf("sip:%s", sip->username);
2147 gchar *tmp = get_contact(sip);
2148 gchar *hdr = g_strdup_printf(
2149 "Event: vnd-microsoft-roaming-self\r\n"
2150 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2151 "Supported: com.microsoft.autoextend\r\n"
2152 "Supported: ms-benotify\r\n"
2153 "Proxy-Require: ms-benotify\r\n"
2154 "Supported: ms-piggyback-first-notify\r\n"
2155 "Contact: %s\r\n"
2156 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2158 gchar *body=g_strdup(
2159 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2160 "<roaming type=\"categories\"/>"
2161 "<roaming type=\"containers\"/>"
2162 "<roaming type=\"subscribers\"/></roamingList>");
2164 g_free(tmp);
2165 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
2166 g_free(body);
2167 g_free(to);
2168 g_free(hdr);
2171 /** Subscription for provisioning information to help with initial
2172 * configuration. This subscription is a one-time query (denoted by the Expires header,
2173 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2174 * configuration, meeting policies, and policy settings that Communicator must enforce.
2175 * TODO: for what we need this information.
2178 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2180 gchar *to = g_strdup_printf("sip:%s", sip->username);
2181 gchar *tmp = get_contact(sip);
2182 gchar *hdr = g_strdup_printf(
2183 "Event: vnd-microsoft-provisioning-v2\r\n"
2184 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2185 "Supported: com.microsoft.autoextend\r\n"
2186 "Supported: ms-benotify\r\n"
2187 "Proxy-Require: ms-benotify\r\n"
2188 "Supported: ms-piggyback-first-notify\r\n"
2189 "Expires: 0\r\n"
2190 "Contact: %s\r\n"
2191 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2192 gchar *body = g_strdup(
2193 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2194 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2195 "<provisioningGroup name=\"ucPolicy\"/>"
2196 "</provisioningGroupList>");
2198 g_free(tmp);
2199 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
2200 g_free(body);
2201 g_free(to);
2202 g_free(hdr);
2205 /* IM Session (INVITE and MESSAGE methods) */
2207 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2209 struct sip_im_session *session;
2210 GSList *entry;
2211 if (sip == NULL || who == NULL) {
2212 return NULL;
2215 entry = sip->im_sessions;
2216 while (entry) {
2217 session = entry->data;
2218 if ((who != NULL && !strcmp(who, session->with))) {
2219 return session;
2221 entry = entry->next;
2223 return NULL;
2226 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2228 struct sip_im_session *session = find_im_session(sip, who);
2229 if (!session) {
2230 session = g_new0(struct sip_im_session, 1);
2231 session->with = g_strdup(who);
2232 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2234 return session;
2237 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2239 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2240 // TODO free session resources
2243 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2245 char *msg, *msg_tmp;
2246 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2247 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2248 g_free(msg_tmp);
2249 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2250 "possibly because one or more persons are offline:\n%s") ,
2251 msg ? msg : "");
2252 purple_conv_present_error(with, sip->account, msg_tmp);
2253 g_free(msg);
2254 g_free(msg_tmp);
2257 static void sipe_im_remove_first_from_queue (struct sip_im_session * session);
2258 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2260 static gboolean
2261 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2263 gboolean ret = TRUE;
2264 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2265 struct sip_im_session * session = find_im_session(sip, with);
2266 struct sip_dialog *dialog;
2268 if (!session) {
2269 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2270 ret = FALSE;
2273 if (msg->response != 200) {
2274 gchar *queued_msg = NULL;
2275 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2277 if (session->outgoing_message_queue) {
2278 queued_msg = session->outgoing_message_queue->data;
2280 sipe_present_message_undelivered_err(with, sip, queued_msg);
2281 im_session_destroy(sip, session);
2282 ret = FALSE;
2285 dialog = session->dialog;
2286 if (!dialog) {
2287 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2288 ret = FALSE;
2291 sipe_im_remove_first_from_queue(session);
2292 sipe_im_process_queue(sip, session);
2293 g_free(with);
2294 return ret;
2297 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2299 gchar *hdr;
2300 gchar *fullto;
2301 gchar *tmp;
2302 char *msgformat;
2303 char *msgtext;
2304 gchar *msgr_value;
2305 gchar *msgr;
2307 if (strncmp("sip:", session->with, 4)) {
2308 fullto = g_strdup_printf("sip:%s", session->with);
2309 } else {
2310 fullto = g_strdup(session->with);
2313 sipe_parse_html(msg, &msgformat, &msgtext);
2314 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2316 msgr_value = sipmsg_get_msgr_string(msgformat);
2317 msgr = "";
2318 g_free(msgformat);
2319 if (msgr_value) {
2320 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2321 g_free(msgr_value);
2324 hdr = g_strdup_printf("Content-Type: text/plain; charset=UTF-8%s\r\n", msgr);
2325 g_free(msgr);
2326 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2327 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2328 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2330 tmp = get_contact(sip);
2331 hdr = g_strdup_printf("Contact: %s\r\n%s", tmp, hdr);
2332 g_free(tmp);
2334 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2335 g_free(msgtext);
2337 g_free(hdr);
2338 g_free(fullto);
2342 static void
2343 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2345 GSList *entry = session->outgoing_message_queue;
2346 if (entry) {
2347 char *queued_msg = entry->data;
2348 sipe_send_message(sip, session, queued_msg);
2352 static void
2353 sipe_im_remove_first_from_queue (struct sip_im_session * session)
2355 if (session && session->outgoing_message_queue) {
2356 char *queued_msg = session->outgoing_message_queue->data;
2357 // Remove from the queue and free the string
2358 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2359 g_free(queued_msg);
2363 static void
2364 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2366 GSList *hdr = msg->headers;
2367 struct siphdrelement *elem;
2368 gchar *contact;
2370 while(hdr)
2372 elem = hdr->data;
2373 if(!strcmp(elem->name, "Record-Route"))
2375 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2376 dialog->routes = g_slist_append(dialog->routes, route);
2378 hdr = g_slist_next(hdr);
2381 if (outgoing)
2383 dialog->routes = g_slist_reverse(dialog->routes);
2386 if (dialog->routes)
2388 dialog->request = dialog->routes->data;
2389 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2392 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2393 dialog->routes = g_slist_append(dialog->routes, contact);
2396 static void
2397 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2399 GSList *hdr = msg->headers;
2400 struct siphdrelement *elem;
2401 while(hdr)
2403 elem = hdr->data;
2404 if(!strcmp(elem->name, "Supported")
2405 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2407 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2410 hdr = g_slist_next(hdr);
2414 static void
2415 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2417 gchar *us = outgoing ? "From" : "To";
2418 gchar *them = outgoing ? "To" : "From";
2420 dialog->callid = sipmsg_find_header(msg, "Call-ID");
2421 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2422 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2423 if (!dialog->theirepid) {
2424 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2426 if (!dialog->theirepid) {
2427 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2430 sipe_get_route_header(msg, dialog, outgoing);
2431 sipe_get_supported_header(msg, dialog, outgoing);
2435 static gboolean
2436 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2438 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2439 struct sip_im_session * session = find_im_session(sip, with);
2440 struct sip_dialog *dialog;
2442 if (!session) {
2443 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2444 g_free(with);
2445 return FALSE;
2448 if (msg->response != 200) {
2449 gchar *queued_msg = NULL;
2450 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2452 if (session->outgoing_message_queue) {
2453 queued_msg = session->outgoing_message_queue->data;
2455 sipe_present_message_undelivered_err(with, sip, queued_msg);
2457 im_session_destroy(sip, session);
2458 g_free(with);
2459 return FALSE;
2462 dialog = session->dialog;
2463 if (!dialog) {
2464 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2465 g_free(with);
2466 return FALSE;
2469 sipe_parse_dialog(msg, dialog, TRUE);
2470 dialog->cseq = 0;
2472 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2473 session->outgoing_invite = NULL;
2474 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2475 sipe_im_remove_first_from_queue(session);
2476 } else {
2477 sipe_im_process_queue(sip, session);
2480 g_free(with);
2481 return TRUE;
2485 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
2487 gchar *hdr;
2488 gchar *to;
2489 gchar *contact;
2490 gchar *body;
2491 char *msgformat;
2492 char *msgtext;
2493 char *base64_msg;
2494 char *ms_text_format;
2495 gchar *msgr_value;
2496 gchar *msgr;
2498 if (session->dialog) {
2499 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2500 return;
2503 session->dialog = g_new0(struct sip_dialog, 1);
2505 if (strstr(session->with, "sip:")) {
2506 to = g_strdup(session->with);
2507 } else {
2508 to = g_strdup_printf("sip:%s", session->with);
2511 sipe_parse_html(msg_body, &msgformat, &msgtext);
2512 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2514 msgr_value = sipmsg_get_msgr_string(msgformat);
2515 g_free(msgformat);
2516 msgr = "";
2517 if (msgr_value) {
2518 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2519 g_free(msgr_value);
2522 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2523 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2524 g_free(msgtext);
2525 g_free(msgr);
2526 g_free(base64_msg);
2528 contact = get_contact(sip);
2529 hdr = g_strdup_printf(
2530 "Contact: %s\r\n%s"
2531 "Content-Type: application/sdp\r\n",
2532 contact, ms_text_format);
2533 g_free(ms_text_format);
2535 body = g_strdup_printf(
2536 "v=0\r\n"
2537 "o=- 0 0 IN IP4 %s\r\n"
2538 "s=session\r\n"
2539 "c=IN IP4 %s\r\n"
2540 "t=0 0\r\n"
2541 "m=message %d sip null\r\n"
2542 "a=accept-types:text/plain text/html image/gif "
2543 "multipart/alternative application/im-iscomposing+xml\r\n",
2544 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2546 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2547 to, to, hdr, body, session->dialog, process_invite_response);
2549 g_free(to);
2550 g_free(body);
2551 g_free(hdr);
2552 g_free(contact);
2555 static void
2556 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2558 if (session) {
2559 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2560 im_session_destroy(sip, session);
2564 static void
2565 sipe_convo_closed(PurpleConnection * gc, const char *who)
2567 struct sipe_account_data *sip = gc->proto_data;
2569 purple_debug_info("sipe", "conversation with %s closed\n", who);
2570 im_session_close(sip, find_im_session(sip, who));
2573 static void
2574 im_session_close_all (struct sipe_account_data *sip)
2576 GSList *entry = sip->im_sessions;
2577 while (entry) {
2578 im_session_close (sip, entry->data);
2579 entry = sip->im_sessions;
2583 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2585 struct sipe_account_data *sip;
2586 char *to;
2587 char *text;
2588 struct sip_im_session *session;
2590 purple_debug_info("sipe", "sipe_im_send what=%s\n", what);
2592 sip = gc->proto_data;
2593 to = g_strdup(who);
2594 text = g_strdup(what);
2596 session = find_or_create_im_session(sip, who);
2598 // Queue the message
2599 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
2601 if (session->dialog && session->dialog->callid) {
2602 sipe_im_process_queue(sip, session);
2603 } else if (!session->outgoing_invite) {
2604 // Need to send the INVITE to get the outgoing dialog setup
2605 sipe_invite(sip, session, text);
2608 g_free(to);
2609 return 1;
2613 /* End IM Session (INVITE and MESSAGE methods) */
2615 static unsigned int
2616 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2618 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2619 struct sip_im_session *session;
2621 if (state == PURPLE_NOT_TYPING)
2622 return 0;
2624 session = find_im_session(sip, who);
2626 if (session && session->dialog) {
2627 send_sip_request(gc, "INFO", who, who,
2628 "Content-Type: application/xml\r\n",
2629 SIPE_SEND_TYPING, session->dialog, NULL);
2632 return SIPE_TYPING_SEND_TIMEOUT;
2635 static gboolean resend_timeout(struct sipe_account_data *sip)
2637 GSList *tmp = sip->transactions;
2638 time_t currtime = time(NULL);
2639 while (tmp) {
2640 struct transaction *trans = tmp->data;
2641 tmp = tmp->next;
2642 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2643 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2644 /* TODO 408 */
2645 } else {
2646 if ((currtime - trans->time > 2) && trans->retries == 0) {
2647 trans->retries++;
2648 sendout_sipmsg(sip, trans->msg);
2652 return TRUE;
2655 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2657 /* register again when security token expires */
2658 /* we have to start a new authentication as the security token
2659 * is almost expired by sending a not signed REGISTER message */
2660 purple_debug_info("sipe", "do a full reauthentication\n");
2661 sipe_auth_free(&sip->registrar);
2662 sip->registerstatus = 0;
2663 do_register(sip);
2664 sip->reauthenticate_set = FALSE;
2667 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2669 gchar *from;
2670 gchar *contenttype;
2671 gboolean found = FALSE;
2673 from = parse_from(sipmsg_find_header(msg, "From"));
2675 if (!from) return;
2677 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2679 contenttype = sipmsg_find_header(msg, "Content-Type");
2680 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2681 gchar *msgr = sipmsg_find_part_of_header(contenttype, "msgr=", NULL, NULL);
2682 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2684 gchar *body_esc = g_markup_escape_text(msg->body, -1);
2685 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2686 g_free(msgr);
2687 g_free(body_esc);
2688 g_free(x_mms_im_format);
2690 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2691 g_free(body_html);
2692 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2693 found = TRUE;
2695 if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2696 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2697 xmlnode *state;
2698 gchar *statedata;
2700 if (!isc) {
2701 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2702 return;
2705 state = xmlnode_get_child(isc, "state");
2707 if (!state) {
2708 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2709 xmlnode_free(isc);
2710 return;
2713 statedata = xmlnode_get_data(state);
2714 if (statedata) {
2715 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2716 else serv_got_typing_stopped(sip->gc, from);
2718 g_free(statedata);
2720 xmlnode_free(isc);
2721 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2722 found = TRUE;
2724 if (!found) {
2725 purple_debug_info("sipe", "got unknown mime-type");
2726 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2728 g_free(from);
2731 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2733 gchar *ms_text_format;
2734 gchar *from;
2735 struct sip_im_session *session;
2736 // Only accept text invitations
2737 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2738 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2739 return;
2742 from = parse_from(sipmsg_find_header(msg, "From"));
2743 session = find_or_create_im_session (sip, from);
2744 if (session) {
2745 if (session->dialog) {
2746 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2747 } else {
2748 session->dialog = g_new0(struct sip_dialog, 1);
2750 sipe_parse_dialog(msg, session->dialog, FALSE);
2752 session->dialog->callid = sipmsg_find_header(msg, "Call-ID");
2753 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2754 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2755 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2757 } else {
2758 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2761 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2762 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2763 if (ms_text_format && !strncmp(ms_text_format, "text/plain", 10)) {
2764 gchar *msgr = sipmsg_find_part_of_header(ms_text_format, "msgr=", ";", NULL);
2765 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2767 gchar *ms_body = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
2768 g_free(msgr);
2769 if (ms_body) {
2770 gchar *body = purple_base64_decode(ms_body, NULL);
2771 gchar *body_esc = g_markup_escape_text(body, -1);
2772 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2773 g_free(ms_body);
2774 g_free(body_esc);
2775 g_free(body);
2776 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2777 g_free(body_html);
2778 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2780 g_free(x_mms_im_format);
2782 g_free(from);
2784 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2785 sipmsg_remove_header(msg, "Ms-Text-Format");
2786 sipmsg_remove_header(msg, "EndPoints");
2787 sipmsg_remove_header(msg, "User-Agent");
2788 sipmsg_remove_header(msg, "Roster-Manager");
2790 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2791 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2793 send_sip_response(sip->gc, msg, 200, "OK", g_strdup_printf(
2794 "v=0\r\n"
2795 "o=- 0 0 IN IP4 %s\r\n"
2796 "s=session\r\n"
2797 "c=IN IP4 %s\r\n"
2798 "t=0 0\r\n"
2799 "m=message %d sip sip:%s\r\n"
2800 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2801 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2802 sip->realport, sip->username));
2805 static void sipe_connection_cleanup(struct sipe_account_data *);
2806 static void create_connection(struct sipe_account_data *, gchar *, int);
2808 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2810 gchar *tmp;
2811 //gchar krb5_token;
2812 const gchar *expires_header;
2813 int expires;
2815 expires_header = sipmsg_find_header(msg, "Expires");
2816 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2817 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2819 switch (msg->response) {
2820 case 200:
2821 if (expires == 0) {
2822 sip->registerstatus = 0;
2823 } else {
2824 int i = 0;
2825 gchar *contact_hdr = NULL;
2826 gchar *gruu = NULL;
2827 gchar *epid;
2828 gchar *uuid;
2830 sip->registerexpire = expires;
2832 if (!sip->reregister_set) {
2833 gchar *action_name = g_strdup_printf("<%s>", "registration");
2834 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
2835 g_free(action_name);
2836 sip->reregister_set = TRUE;
2839 sip->registerstatus = 3;
2841 if (!sip->reauthenticate_set) {
2842 /* we have to reauthenticate as our security token expires
2843 after eight hours (be five minutes early) */
2844 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
2845 guint reauth_timeout = (8 * 3600) - 360;
2846 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
2847 g_free(action_name);
2848 sip->reauthenticate_set = TRUE;
2851 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
2853 epid = get_epid();
2854 uuid = generateUUIDfromEPID(epid);
2855 g_free(epid);
2857 // There can be multiple Contact headers (one per location where the user is logged in) so
2858 // make sure to only get the one for this uuid
2859 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
2860 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
2861 if (valid_contact) {
2862 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
2863 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
2864 g_free(valid_contact);
2865 break;
2866 } else {
2867 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
2870 g_free(uuid);
2872 if(gruu) {
2873 sip->contact = g_strdup_printf("<%s>", gruu);
2874 g_free(gruu);
2875 } else {
2876 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
2877 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);
2880 if (!sip->subscribed) { //do it just once, not every re-register
2881 tmp = sipmsg_find_header(msg, "Allow-Events");
2882 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
2883 sipe_subscribe_buddylist(sip, msg);
2885 sipe_subscribe_acl(sip, msg);
2886 sipe_subscribe_roaming_self(sip, msg);
2887 sipe_subscribe_roaming_provisioning(sip, msg);
2888 sipe_subscribe_pending_buddies(sip, msg);
2889 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
2890 sip->subscribed = TRUE;
2893 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
2894 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
2895 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
2896 } else {
2897 tmp = sipmsg_find_header(msg, "ms-keep-alive");
2898 if (tmp) {
2899 sipe_keep_alive_timeout(sip, tmp);
2903 // Should we remove the transaction here?
2904 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
2905 transactions_remove(sip, tc);
2907 break;
2908 case 301:
2910 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
2912 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
2913 gchar **parts = g_strsplit(redirect + 4, ";", 0);
2914 gchar **tmp;
2915 gchar *hostname;
2916 int port = 0;
2917 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
2918 int i = 1;
2920 tmp = g_strsplit(parts[0], ":", 0);
2921 hostname = g_strdup(tmp[0]);
2922 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
2923 g_strfreev(tmp);
2925 while (parts[i]) {
2926 tmp = g_strsplit(parts[i], "=", 0);
2927 if (tmp[1]) {
2928 if (g_strcasecmp("transport", tmp[0]) == 0) {
2929 if (g_strcasecmp("tcp", tmp[1]) == 0) {
2930 transport = SIPE_TRANSPORT_TCP;
2931 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
2932 transport = SIPE_TRANSPORT_UDP;
2936 g_strfreev(tmp);
2937 i++;
2939 g_strfreev(parts);
2941 /* Close old connection */
2942 sipe_connection_cleanup(sip);
2944 /* Create new connection */
2945 sip->transport = transport;
2946 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
2947 hostname, port, TRANSPORT_DESCRIPTOR);
2948 create_connection(sip, hostname, port);
2951 break;
2952 case 401:
2953 if (sip->registerstatus != 2) {
2954 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
2955 if (sip->registrar.retries > 3) {
2956 sip->gc->wants_to_die = TRUE;
2957 purple_connection_error(sip->gc, _("Wrong Password"));
2958 return TRUE;
2960 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
2961 tmp = sipmsg_find_auth_header(msg, "NTLM");
2962 } else {
2963 tmp = sipmsg_find_auth_header(msg, "Kerberos");
2965 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
2966 fill_auth(sip, tmp, &sip->registrar);
2967 sip->registerstatus = 2;
2968 if (sip->account->disconnecting) {
2969 do_register_exp(sip, 0);
2970 } else {
2971 do_register(sip);
2974 break;
2975 case 403:
2977 const gchar *warning = sipmsg_find_header(msg, "Warning");
2978 if (warning != NULL) {
2979 /* Example header:
2980 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
2982 gchar **tmp = g_strsplit(warning, "\"", 0);
2983 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
2984 g_strfreev(tmp);
2985 } else {
2986 warning = _("You have been rejected by the server");
2989 sip->gc->wants_to_die = TRUE;
2990 purple_connection_error(sip->gc, warning);
2991 return TRUE;
2993 break;
2994 case 404:
2996 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2997 if (warning != NULL) {
2998 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2999 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3000 g_free(reason);
3001 } else {
3002 warning = _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3005 sip->gc->wants_to_die = TRUE;
3006 purple_connection_error(sip->gc, warning);
3007 return TRUE;
3009 break;
3010 case 503:
3012 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3013 if (warning != NULL) {
3014 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3015 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3016 g_free(reason);
3017 } else {
3018 warning = _("Service unavailable: no reason given");
3021 sip->gc->wants_to_die = TRUE;
3022 purple_connection_error(sip->gc, warning);
3023 return TRUE;
3025 break;
3027 return TRUE;
3030 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3032 const char *uri;
3033 xmlnode *xn_categories;
3034 xmlnode *xn_category;
3035 xmlnode *xn_node;
3036 int changed = 0;
3037 const char *activity = NULL;
3039 xn_categories = xmlnode_from_str(data, len);
3040 uri = xmlnode_get_attrib(xn_categories, "uri");
3042 purple_debug_info("sipe", "process_incoming_notify_rlmi\n");
3044 for (xn_category = xmlnode_get_child(xn_categories, "category");
3045 xn_category ;
3046 xn_category = xmlnode_get_next_twin(xn_category) )
3048 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3050 if (!strcmp(attrVar, "note"))
3052 char *note;
3053 struct sipe_buddy *sbuddy;
3054 xn_node = xmlnode_get_child(xn_category, "note");
3055 if (!xn_node) continue;
3056 xn_node = xmlnode_get_child(xn_node, "body");
3057 if (!xn_node) continue;
3059 note = xmlnode_get_data(xn_node);
3061 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3062 if (sbuddy && note)
3064 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3065 sbuddy->annotation = g_strdup(note);
3066 changed = 1;
3069 g_free(note);
3071 else if(!strcmp(attrVar, "state"))
3073 char *data;
3074 int avail;
3075 xn_node = xmlnode_get_child(xn_category, "state");
3076 if (!xn_node) continue;
3077 xn_node = xmlnode_get_child(xn_node, "availability");
3078 if (!xn_node) continue;
3080 data = xmlnode_get_data(xn_node);
3081 avail = atoi(data);
3082 g_free(data);
3084 if (avail < 3000)
3085 activity = "unknown";
3086 else if (avail < 4500)
3087 activity = "available";
3088 else if (avail < 6000)
3089 activity = "idle";
3090 else if (avail < 7500)
3091 activity = "busy";
3092 else if (avail < 9000)
3093 activity = "busy";
3094 else if (avail < 12000)
3095 activity = "dnd";
3096 else if (avail < 18000)
3097 activity = "away";
3098 else
3099 activity = "offline";
3101 changed = 1;
3104 if (changed)
3106 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3109 xmlnode_free(xn_categories);
3112 static void process_incoming_notify_pidf(struct sipe_account_data *sip, struct sipmsg *msg)
3114 gchar *fromhdr;
3115 gchar *from;
3116 gchar *getbasic = g_strdup("closed");
3117 gchar *activity = g_strdup("available");
3118 xmlnode *pidf;
3119 xmlnode *basicstatus = NULL, *tuple, *status;
3120 gboolean isonline = FALSE;
3121 xmlnode *display_name_node;
3123 fromhdr = sipmsg_find_header(msg, "From");
3124 from = parse_from(fromhdr);
3126 if (!from) {
3127 return;
3130 pidf = xmlnode_from_str(msg->body, msg->bodylen);
3131 if (!pidf) {
3132 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",msg->body);
3133 return;
3136 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3138 if ((status = xmlnode_get_child(tuple, "status"))) {
3139 basicstatus = xmlnode_get_child(status, "basic");
3143 if (!basicstatus) {
3144 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3145 xmlnode_free(pidf);
3146 return;
3149 getbasic = xmlnode_get_data(basicstatus);
3150 if (!getbasic) {
3151 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3152 xmlnode_free(pidf);
3153 return;
3156 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3157 if (strstr(getbasic, "open")) {
3158 isonline = TRUE;
3161 display_name_node = xmlnode_get_child(pidf, "display-name");
3162 if (display_name_node) {
3163 PurpleBuddy * buddy = purple_find_buddy (sip->account, from);
3164 char * display_name = xmlnode_get_data(display_name_node);
3165 if (buddy && display_name) {
3166 purple_blist_server_alias_buddy (buddy, g_strdup(display_name));
3170 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3171 if ((status = xmlnode_get_child(tuple, "status"))) {
3172 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3173 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3174 activity = xmlnode_get_data(basicstatus);
3180 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3182 if (isonline) {
3183 gchar * status_id = NULL;
3184 if (activity) {
3185 if (strstr(activity, "busy")) {
3186 status_id = "busy";
3187 } else if (strstr(activity, "away")) {
3188 status_id = "away";
3192 if (!status_id) {
3193 status_id = "available";
3196 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3197 purple_prpl_got_user_status(sip->account, from, status_id, NULL);
3198 } else {
3199 purple_prpl_got_user_status(sip->account, from, "offline", NULL);
3202 xmlnode_free(pidf);
3203 g_free(from);
3204 g_free(getbasic);
3205 g_free(activity);
3208 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, struct sipmsg *msg)
3210 const char *availability;
3211 const char *activity;
3212 const char *display_name = NULL;
3213 const char *activity_name;
3214 const char *name;
3215 char *uri;
3216 int avl;
3217 int act;
3218 struct sipe_buddy *sbuddy;
3220 xmlnode *xn_presentity = xmlnode_from_str(msg->body, msg->bodylen);
3222 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3223 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3224 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3225 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3226 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3227 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3228 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3229 const char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3230 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3231 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3232 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3233 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3235 name = xmlnode_get_attrib(xn_presentity, "uri");
3236 uri = g_strdup_printf("sip:%s", name);
3237 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3238 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3240 // updating display name if alias was just URI
3241 if (xn_display_name) {
3242 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3243 GSList *entry = buddies;
3244 PurpleBuddy *p_buddy;
3245 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3247 while (entry) {
3248 const char *email_str, *server_alias;
3250 p_buddy = entry->data;
3252 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3253 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3254 purple_blist_alias_buddy(p_buddy, display_name);
3257 server_alias = purple_buddy_get_server_alias(p_buddy);
3258 if (display_name &&
3259 ( (server_alias && strcmp(display_name, server_alias))
3260 || !server_alias || strlen(server_alias) == 0 )
3262 purple_blist_server_alias_buddy(p_buddy, display_name);
3265 if (email) {
3266 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3267 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3268 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3272 entry = entry->next;
3276 avl = atoi(availability);
3277 act = atoi(activity);
3279 if (act <= 100)
3280 activity_name = "away";
3281 else if (act <= 150)
3282 activity_name = "out-to-lunch";
3283 else if (act <= 300)
3284 activity_name = "be-right-back";
3285 else if (act <= 400)
3286 activity_name = "available";
3287 else if (act <= 500)
3288 activity_name = "on-the-phone";
3289 else if (act <= 600)
3290 activity_name = "busy";
3291 else
3292 activity_name = "available";
3294 if (avl == 0)
3295 activity_name = "offline";
3297 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3298 if (sbuddy)
3300 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3301 sbuddy->annotation = NULL;
3302 if (note) { sbuddy->annotation = g_strdup(note); }
3304 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3305 sbuddy->device_name = NULL;
3306 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3309 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3310 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3311 xmlnode_free(xn_presentity);
3312 g_free(uri);
3315 static void process_incoming_notify_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3317 char *ctype = sipmsg_find_header(msg, "Content-Type");
3319 purple_debug_info("sipe", "process_incoming_notify_presence: Content-Type: %s\n", ctype ? ctype : "");
3321 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3322 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3324 const char *content = msg->body;
3325 unsigned length = msg->bodylen;
3326 PurpleMimeDocument *mime = NULL;
3327 //char *subscription_state;
3329 if (strstr(ctype, "multipart"))
3331 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3332 GList* parts;
3333 mime = purple_mime_document_parse(doc);
3334 parts = purple_mime_document_get_parts(mime);
3335 content = purple_mime_part_get_data(parts->data);
3336 length = purple_mime_part_get_length(parts->data);
3337 g_free(doc);
3340 process_incoming_notify_rlmi(sip, content, length);
3342 if (mime)
3344 purple_mime_document_free(mime);
3347 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3349 process_incoming_notify_msrtc(sip, msg);
3351 else
3353 process_incoming_notify_pidf(sip, msg);
3358 * Dispatcher for all incoming subscription information
3359 * whether it comes from NOTIFY, BENOTIFY requests or
3360 * piggy-backed to subscription's OK responce.
3362 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3363 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3365 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3367 gchar *event = sipmsg_find_header(msg, "Event");
3368 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3369 const char *uri,*state;
3370 xmlnode *xn_list;
3371 xmlnode *xn_resource;
3372 xmlnode *xn_instance;
3374 int expires = 0;
3376 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3377 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3379 if (!request)
3381 const gchar *expires_header;
3382 expires_header = sipmsg_find_header(msg, "Expires");
3383 expires = expires_header ? strtol(expires_header, NULL, 10) : 0;
3384 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires);
3387 if (!subscription_state || strstr(subscription_state, "active"))
3389 if (event && strstr(event, "presence"))
3391 process_incoming_notify_presence(sip, msg);
3393 else if (event && strstr(event, "vnd-microsoft-roaming-contacts"))
3395 sipe_process_roaming_contacts(sip, msg, NULL);
3397 else if (event && strstr(event, "vnd-microsoft-roaming-self"))
3399 sipe_process_roaming_self(sip, msg);
3401 else if (event && strstr(event, "vnd-microsoft-roaming-ACL"))
3403 sipe_process_roaming_acl(sip, msg);
3405 else if (event && strstr(event, "presence.wpending"))
3407 sipe_process_incoming_pending(sip, msg);
3409 else
3411 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3415 //Subscription terminated and is not a (BE)NOTIFY; we need resub
3416 if ( !request && !benotify && subscription_state && strstr(subscription_state, "terminated"))
3418 if(event && strstr(event, "presence")){
3420 const char *content = msg->body;
3421 unsigned length = msg->bodylen;
3422 PurpleMimeDocument *mime = NULL;
3423 char *ctype = sipmsg_find_header(msg, "Content-Type");
3425 purple_debug_info("sipe", "process_incoming_notify: ctype(%s)\n",ctype);
3427 if (ctype && strstr(ctype, "multipart"))
3429 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3430 GList* parts;
3431 mime = purple_mime_document_parse(doc);
3432 parts = purple_mime_document_get_parts(mime);
3433 content = purple_mime_part_get_data(parts->data);
3434 length = purple_mime_part_get_length(parts->data);
3435 g_free(doc);
3438 xn_list = xmlnode_from_str(content, length);
3439 xn_resource = xmlnode_get_child(xn_list, "resource");
3440 if (!xn_resource) return;
3441 xn_instance = xmlnode_get_child(xn_resource, "instance");
3442 if (!xn_instance) return;
3443 state = xmlnode_get_attrib(xn_instance, "state");
3444 uri = xmlnode_get_attrib(xn_instance, "cid");
3446 purple_debug_info("sipe", "process_incoming_notify: cid(%s),state(%s)\n",uri,state);
3448 int timeout = expires ? expires : 3;
3449 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", uri);
3451 if(strstr(state, "resubscribe"))
3453 sipe_cancel_scheduled_action(sip, action_name);
3454 purple_debug_info("sipe", "process_incoming_notify: Subscription to buddy %s was terminated. Resubscribing\n", uri);
3455 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_to_name_single, sip, g_strdup(uri));
3456 g_free(action_name);
3459 else
3461 purple_debug_info("sipe", "Unable to process subscription termination. Event is not supported:%s\n",
3462 event ? event : "");
3466 //The server sends a (BE)NOTIFY with the status 'terminated'
3467 if(request && subscription_state && strstr(subscription_state, "terminated") )
3469 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3470 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3471 g_free(from);
3474 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3475 if (request && !benotify)
3477 sipmsg_remove_header(msg, "Expires");
3478 sipmsg_remove_header(msg, "subscription-state");
3479 sipmsg_remove_header(msg, "Event");
3480 sipmsg_remove_header(msg, "Require");
3481 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3486 * unused. Needed?
3488 static gchar* gen_xpidf(struct sipe_account_data *sip)
3490 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3491 "<presence>\r\n"
3492 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3493 "<display name=\"sip:%s\"/>\r\n"
3494 "<atom id=\"1234\">\r\n"
3495 "<address uri=\"sip:%s\">\r\n"
3496 "<status status=\"%s\"/>\r\n"
3497 "</address>\r\n"
3498 "</atom>\r\n"
3499 "</presence>\r\n",
3500 sip->username,
3501 sip->username,
3502 sip->username,
3503 sip->status);
3504 return doc;
3509 static gchar* gen_pidf(struct sipe_account_data *sip)
3511 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3512 "<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"
3513 "<tuple id=\"0\">\r\n"
3514 "<status>\r\n"
3515 "<basic>open</basic>\r\n"
3516 "<ep:activities>\r\n"
3517 " <ep:activity>%s</ep:activity>\r\n"
3518 "</ep:activities>"
3519 "</status>\r\n"
3520 "</tuple>\r\n"
3521 "<ci:display-name>%s</ci:display-name>\r\n"
3522 "</presence>",
3523 sip->username,
3524 sip->status,
3525 sip->username);
3526 return doc;
3530 static gboolean
3531 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3533 if (msg->response == 488) {
3534 sip->presence_method_version = 1;
3535 send_presence_info(sip);
3537 return TRUE;
3540 static void send_presence_info_v0(struct sipe_account_data *sip, char * note)
3542 int availability = 300; // online
3543 int activity = 400; // Available
3544 gchar *name;
3545 gchar *body;
3546 if (!strcmp(sip->status, "away")) {
3547 activity = 100;
3548 } else if (!strcmp(sip->status, "out-to-lunch")) {
3549 activity = 150;
3550 } else if (!strcmp(sip->status, "be-right-back")) {
3551 activity = 300;
3552 } else if (!strcmp(sip->status, "on-the-phone")) {
3553 activity = 500;
3554 } else if (!strcmp(sip->status, "do-not-disturb")) {
3555 activity = 600;
3556 } else if (!strcmp(sip->status, "busy")) {
3557 activity = 600;
3558 } else if (!strcmp(sip->status, "invisible")) {
3559 availability = 0; // offline
3560 activity = 100;
3563 name = g_strdup_printf("sip: sip:%s", sip->username);
3564 //@TODO: send user data - state; add hostname in upper case
3565 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3566 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
3567 g_free(name);
3568 g_free(body);
3571 static gboolean
3572 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3574 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3575 if (msg->response == 200) {
3576 sip->status_version = 0;
3577 send_presence_info(sip);
3579 return TRUE;
3582 static gboolean
3583 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3585 if (msg->response == 409) {
3586 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3587 // TODO need to parse the version #'s?
3588 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3589 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3591 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg->body);
3593 gchar *tmp = get_contact(sip);
3594 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
3595 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3597 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3599 g_free(tmp);
3600 g_free(hdr);
3601 g_free(uri);
3602 g_free(doc);
3604 return TRUE;
3607 static void send_presence_info_v1(struct sipe_account_data *sip, char * note)
3609 int code;
3610 gchar *uri;
3611 gchar *doc;
3612 gchar *tmp;
3613 gchar *hdr;
3614 if (!strcmp(sip->status, "away")) {
3615 code = 12000;
3616 } else if (!strcmp(sip->status, "busy")) {
3617 code = 6000;
3618 } else {
3619 // Available
3620 code = 3000;
3623 uri = g_strdup_printf("sip:%s", sip->username);
3624 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3625 sip->status_version, code,
3626 sip->status_version, code,
3627 sip->status_version, note ? note : "",
3628 sip->status_version, note ? note : "",
3629 sip->status_version, note ? note : ""
3631 sip->status_version++;
3633 tmp = get_contact(sip);
3634 hdr = g_strdup_printf("Contact: %s\r\n"
3635 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3637 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
3639 g_free(tmp);
3640 g_free(hdr);
3641 g_free(uri);
3642 g_free(doc);
3645 static void send_presence_info(struct sipe_account_data *sip)
3647 PurpleStatus * status = purple_account_get_active_status(sip->account);
3648 gchar *note;
3649 if (!status) return;
3651 note = g_strdup(purple_status_get_attr_string(status, "message"));
3653 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
3654 if (sip->presence_method_version != 1) {
3655 send_presence_info_v0(sip, note);
3656 } else {
3657 send_presence_info_v1(sip, note);
3661 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3663 gboolean found = FALSE;
3664 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3665 if (msg->response == 0) { /* request */
3666 if (!strcmp(msg->method, "MESSAGE")) {
3667 process_incoming_message(sip, msg);
3668 found = TRUE;
3669 } else if (!strcmp(msg->method, "NOTIFY")) {
3670 purple_debug_info("sipe","send->process_incoming_notify\n");
3671 process_incoming_notify(sip, msg, TRUE, FALSE);
3672 found = TRUE;
3673 } else if (!strcmp(msg->method, "BENOTIFY")) {
3674 purple_debug_info("sipe","send->process_incoming_benotify\n");
3675 process_incoming_notify(sip, msg, TRUE, TRUE);
3676 found = TRUE;
3677 } else if (!strcmp(msg->method, "INVITE")) {
3678 process_incoming_invite(sip, msg);
3679 found = TRUE;
3680 } else if (!strcmp(msg->method, "INFO")) {
3681 // TODO needs work
3682 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3683 if (from) {
3684 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3686 g_free(from);
3687 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3688 found = TRUE;
3689 } else if (!strcmp(msg->method, "ACK")) {
3690 // ACK's don't need any response
3691 found = TRUE;
3692 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3693 // LCS 2005 sends us these - just respond 200 OK
3694 found = TRUE;
3695 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3696 } else if (!strcmp(msg->method, "BYE")) {
3697 struct sip_im_session *session;
3698 gchar *from;
3699 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3701 from = parse_from(sipmsg_find_header(msg, "From"));
3702 session = find_im_session (sip, from);
3703 g_free(from);
3705 if (session) {
3706 // TODO Let the user know the other user left the conversation?
3707 im_session_destroy(sip, session);
3710 found = TRUE;
3711 } else {
3712 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3714 } else { /* response */
3715 struct transaction *trans = transactions_find(sip, msg);
3716 if (trans) {
3717 if (msg->response == 407) {
3718 gchar *resend, *auth, *ptmp;
3720 if (sip->proxy.retries > 30) return;
3721 sip->proxy.retries++;
3722 /* do proxy authentication */
3724 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3726 fill_auth(sip, ptmp, &sip->proxy);
3727 auth = auth_header(sip, &sip->proxy, trans->msg);
3728 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3729 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
3730 g_free(auth);
3731 resend = sipmsg_to_string(trans->msg);
3732 /* resend request */
3733 sendout_pkt(sip->gc, resend);
3734 g_free(resend);
3735 } else {
3736 if (msg->response == 100 || msg->response == 180) {
3737 /* ignore provisional response */
3738 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
3739 } else {
3740 sip->proxy.retries = 0;
3741 if (!strcmp(trans->msg->method, "REGISTER")) {
3742 if (msg->response == 401)
3744 sip->registrar.retries++;
3745 sip->registrar.expires = 0;
3747 else
3749 sip->registrar.retries = 0;
3751 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
3752 } else {
3753 if (msg->response == 401) {
3754 gchar *resend, *auth, *ptmp;
3756 if (sip->registrar.retries > 4) return;
3757 sip->registrar.retries++;
3759 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3760 ptmp = sipmsg_find_auth_header(msg, "NTLM");
3761 } else {
3762 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
3765 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
3767 fill_auth(sip, ptmp, &sip->registrar);
3768 auth = auth_header(sip, &sip->registrar, trans->msg);
3769 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3770 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
3772 //sipmsg_remove_header(trans->msg, "Authorization");
3773 //sipmsg_add_header(trans->msg, "Authorization", auth);
3774 g_free(auth);
3775 resend = sipmsg_to_string(trans->msg);
3776 /* resend request */
3777 sendout_pkt(sip->gc, resend);
3778 g_free(resend);
3782 if (trans->callback) {
3783 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
3784 /* call the callback to process response*/
3785 (trans->callback)(sip, msg, trans);
3787 /* Not sure if this is needed or what needs to be done
3788 but transactions seem to be removed prematurely so
3789 this only removes them if the response is 200 OK */
3790 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
3791 /*Has a bug and it's unneccesary*/
3792 /*transactions_remove(sip, trans);*/
3796 found = TRUE;
3797 } else {
3798 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
3801 if (!found) {
3802 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
3806 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
3808 char *cur;
3809 char *dummy;
3810 struct sipmsg *msg;
3811 int restlen;
3812 cur = conn->inbuf;
3814 /* according to the RFC remove CRLF at the beginning */
3815 while (*cur == '\r' || *cur == '\n') {
3816 cur++;
3818 if (cur != conn->inbuf) {
3819 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
3820 conn->inbufused = strlen(conn->inbuf);
3823 /* Received a full Header? */
3824 sip->processing_input = TRUE;
3825 while (sip->processing_input &&
3826 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
3827 time_t currtime = time(NULL);
3828 cur += 2;
3829 cur[0] = '\0';
3830 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
3831 msg = sipmsg_parse_header(conn->inbuf);
3832 cur[0] = '\r';
3833 cur += 2;
3834 restlen = conn->inbufused - (cur - conn->inbuf);
3835 if (restlen >= msg->bodylen) {
3836 dummy = g_malloc(msg->bodylen + 1);
3837 memcpy(dummy, cur, msg->bodylen);
3838 dummy[msg->bodylen] = '\0';
3839 msg->body = dummy;
3840 cur += msg->bodylen;
3841 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
3842 conn->inbufused = strlen(conn->inbuf);
3843 } else {
3844 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
3845 restlen, msg->bodylen, (int)strlen(conn->inbuf));
3846 sipmsg_free(msg);
3847 return;
3850 /*if (msg->body) {
3851 purple_debug_info("sipe", "body:\n%s", msg->body);
3854 // Verify the signature before processing it
3855 if (sip->registrar.ntlm_key) {
3856 struct sipmsg_breakdown msgbd;
3857 gchar *signature_input_str;
3858 gchar *signature = NULL;
3859 gchar *rspauth;
3860 msgbd.msg = msg;
3861 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
3862 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
3863 if (signature_input_str != NULL) {
3864 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
3866 g_free(signature_input_str);
3868 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
3870 if (signature != NULL) {
3871 if (rspauth != NULL) {
3872 if (purple_ntlm_verify_signature (signature, rspauth)) {
3873 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
3874 process_input_message(sip, msg);
3875 } else {
3876 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
3877 purple_connection_error(sip->gc, _("Invalid message signature received"));
3878 sip->gc->wants_to_die = TRUE;
3880 } else if (msg->response == 401) {
3881 purple_connection_error(sip->gc, _("Wrong Password"));
3882 sip->gc->wants_to_die = TRUE;
3884 g_free(signature);
3887 g_free(rspauth);
3888 sipmsg_breakdown_free(&msgbd);
3889 } else {
3890 process_input_message(sip, msg);
3893 sipmsg_free(msg);
3897 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
3899 PurpleConnection *gc = data;
3900 struct sipe_account_data *sip = gc->proto_data;
3901 struct sipmsg *msg;
3902 int len;
3903 time_t currtime;
3905 static char buffer[65536];
3906 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
3907 buffer[len] = '\0';
3908 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
3909 msg = sipmsg_parse_msg(buffer);
3910 if (msg) process_input_message(sip, msg);
3914 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
3916 struct sipe_account_data *sip = gc->proto_data;
3917 PurpleSslConnection *gsc = sip->gsc;
3919 purple_debug_error("sipe", "%s",debug);
3920 purple_connection_error(gc, msg);
3922 /* Invalidate this connection. Next send will open a new one */
3923 if (gsc) {
3924 connection_remove(sip, gsc->fd);
3925 purple_ssl_close(gsc);
3927 sip->gsc = NULL;
3928 sip->fd = -1;
3931 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
3933 PurpleConnection *gc = data;
3934 struct sipe_account_data *sip;
3935 struct sip_connection *conn;
3936 int readlen, len;
3937 gboolean firstread = TRUE;
3939 /* NOTE: This check *IS* necessary */
3940 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
3941 purple_ssl_close(gsc);
3942 return;
3945 sip = gc->proto_data;
3946 conn = connection_find(sip, gsc->fd);
3947 if (conn == NULL) {
3948 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
3949 gc->wants_to_die = TRUE;
3950 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
3951 return;
3954 /* Read all available data from the SSL connection */
3955 do {
3956 /* Increase input buffer size as needed */
3957 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
3958 conn->inbuflen += SIMPLE_BUF_INC;
3959 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
3960 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
3963 /* Try to read as much as there is space left in the buffer */
3964 readlen = conn->inbuflen - conn->inbufused - 1;
3965 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
3967 if (len < 0 && errno == EAGAIN) {
3968 /* Try again later */
3969 return;
3970 } else if (len < 0) {
3971 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
3972 return;
3973 } else if (firstread && (len == 0)) {
3974 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
3975 return;
3978 conn->inbufused += len;
3979 firstread = FALSE;
3981 /* Equivalence indicates that there is possibly more data to read */
3982 } while (len == readlen);
3984 conn->inbuf[conn->inbufused] = '\0';
3985 process_input(sip, conn);
3989 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
3991 PurpleConnection *gc = data;
3992 struct sipe_account_data *sip = gc->proto_data;
3993 int len;
3994 struct sip_connection *conn = connection_find(sip, source);
3995 if (!conn) {
3996 purple_debug_error("sipe", "Connection not found!\n");
3997 return;
4000 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4001 conn->inbuflen += SIMPLE_BUF_INC;
4002 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4005 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4007 if (len < 0 && errno == EAGAIN)
4008 return;
4009 else if (len <= 0) {
4010 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4011 connection_remove(sip, source);
4012 if (sip->fd == source) sip->fd = -1;
4013 return;
4016 conn->inbufused += len;
4017 conn->inbuf[conn->inbufused] = '\0';
4019 process_input(sip, conn);
4022 /* Callback for new connections on incoming TCP port */
4023 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4025 PurpleConnection *gc = data;
4026 struct sipe_account_data *sip = gc->proto_data;
4027 struct sip_connection *conn;
4029 int newfd = accept(source, NULL, NULL);
4031 conn = connection_create(sip, newfd);
4033 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4036 static void login_cb(gpointer data, gint source, const gchar *error_message)
4038 PurpleConnection *gc = data;
4039 struct sipe_account_data *sip;
4040 struct sip_connection *conn;
4042 if (!PURPLE_CONNECTION_IS_VALID(gc))
4044 if (source >= 0)
4045 close(source);
4046 return;
4049 if (source < 0) {
4050 purple_connection_error(gc, _("Could not connect"));
4051 return;
4054 sip = gc->proto_data;
4055 sip->fd = source;
4056 sip->last_keepalive = time(NULL);
4058 conn = connection_create(sip, source);
4060 do_register(sip);
4062 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4065 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4067 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4068 if (sip == NULL) return;
4070 do_register(sip);
4073 static guint sipe_ht_hash_nick(const char *nick)
4075 char *lc = g_utf8_strdown(nick, -1);
4076 guint bucket = g_str_hash(lc);
4077 g_free(lc);
4079 return bucket;
4082 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4084 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4087 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4089 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4091 sip->listen_data = NULL;
4093 if (listenfd == -1) {
4094 purple_connection_error(sip->gc, _("Could not create listen socket"));
4095 return;
4098 sip->fd = listenfd;
4100 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4101 sip->listenfd = sip->fd;
4103 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4105 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4106 do_register(sip);
4109 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4111 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4112 int addr_size;
4114 sip->query_data = NULL;
4116 if (!hosts || !hosts->data) {
4117 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4118 return;
4121 addr_size = GPOINTER_TO_INT(hosts->data);
4122 hosts = g_slist_remove(hosts, hosts->data);
4123 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4124 g_free(hosts->data);
4125 hosts = g_slist_remove(hosts, hosts->data);
4126 while (hosts) {
4127 hosts = g_slist_remove(hosts, hosts->data);
4128 g_free(hosts->data);
4129 hosts = g_slist_remove(hosts, hosts->data);
4132 /* create socket for incoming connections */
4133 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4134 sipe_udp_host_resolved_listen_cb, sip);
4135 if (sip->listen_data == NULL) {
4136 purple_connection_error(sip->gc, _("Could not create listen socket"));
4137 return;
4141 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4142 gpointer data)
4144 PurpleConnection *gc = data;
4145 struct sipe_account_data *sip;
4147 /* If the connection is already disconnected, we don't need to do anything else */
4148 if (!PURPLE_CONNECTION_IS_VALID(gc))
4149 return;
4151 sip = gc->proto_data;
4152 sip->fd = -1;
4153 sip->gsc = NULL;
4155 switch(error) {
4156 case PURPLE_SSL_CONNECT_FAILED:
4157 purple_connection_error(gc, _("Connection Failed"));
4158 break;
4159 case PURPLE_SSL_HANDSHAKE_FAILED:
4160 purple_connection_error(gc, _("SSL Handshake Failed"));
4161 break;
4162 case PURPLE_SSL_CERTIFICATE_INVALID:
4163 purple_connection_error(gc, _("SSL Certificate Invalid"));
4164 break;
4168 static void
4169 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4171 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4172 PurpleProxyConnectData *connect_data;
4174 sip->listen_data = NULL;
4176 sip->listenfd = listenfd;
4177 if (sip->listenfd == -1) {
4178 purple_connection_error(sip->gc, _("Could not create listen socket"));
4179 return;
4182 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4183 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4184 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4185 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4186 sipe_newconn_cb, sip->gc);
4187 purple_debug_info("sipe", "connecting to %s port %d\n",
4188 sip->realhostname, sip->realport);
4189 /* open tcp connection to the server */
4190 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4191 sip->realport, login_cb, sip->gc);
4193 if (connect_data == NULL) {
4194 purple_connection_error(sip->gc, _("Couldn't create socket"));
4199 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4201 PurpleAccount *account = sip->account;
4202 PurpleConnection *gc = sip->gc;
4204 if (purple_account_get_bool(account, "useport", FALSE)) {
4205 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4206 port = purple_account_get_int(account, "port", 0);
4207 } else {
4208 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4211 sip->realhostname = hostname;
4212 sip->realport = port;
4214 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4215 hostname, port);
4217 /* TODO: is there a good default grow size? */
4218 if (sip->transport != SIPE_TRANSPORT_UDP)
4219 sip->txbuf = purple_circ_buffer_new(0);
4221 if (sip->transport == SIPE_TRANSPORT_TLS) {
4222 /* SSL case */
4223 if (!purple_ssl_is_supported()) {
4224 gc->wants_to_die = TRUE;
4225 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4226 return;
4229 purple_debug_info("sipe", "using SSL\n");
4231 sip->gsc = purple_ssl_connect(account, hostname, port,
4232 login_cb_ssl, sipe_ssl_connect_failure, gc);
4233 if (sip->gsc == NULL) {
4234 purple_connection_error(gc, _("Could not create SSL context"));
4235 return;
4237 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4238 /* UDP case */
4239 purple_debug_info("sipe", "using UDP\n");
4241 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4242 if (sip->query_data == NULL) {
4243 purple_connection_error(gc, _("Could not resolve hostname"));
4245 } else {
4246 /* TCP case */
4247 purple_debug_info("sipe", "using TCP\n");
4248 /* create socket for incoming connections */
4249 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4250 sipe_tcp_connect_listen_cb, sip);
4251 if (sip->listen_data == NULL) {
4252 purple_connection_error(gc, _("Could not create listen socket"));
4253 return;
4258 /* Service list for autodection */
4259 static const struct sipe_service_data service_autodetect[] = {
4260 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4261 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4262 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4263 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4264 { NULL, NULL, 0 }
4267 /* Service list for SSL/TLS */
4268 static const struct sipe_service_data service_tls[] = {
4269 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4270 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4271 { NULL, NULL, 0 }
4274 /* Service list for TCP */
4275 static const struct sipe_service_data service_tcp[] = {
4276 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4277 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4278 { NULL, NULL, 0 }
4281 /* Service list for UDP */
4282 static const struct sipe_service_data service_udp[] = {
4283 { "sip", "udp", SIPE_TRANSPORT_UDP },
4284 { NULL, NULL, 0 }
4287 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4288 static void resolve_next_service(struct sipe_account_data *sip,
4289 const struct sipe_service_data *start)
4291 if (start) {
4292 sip->service_data = start;
4293 } else {
4294 sip->service_data++;
4295 if (sip->service_data->service == NULL) {
4296 gchar *hostname;
4297 /* Try connecting to the SIP hostname directly */
4298 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4299 if (sip->auto_transport) {
4300 // If SSL is supported, default to using it; OCS servers aren't configured
4301 // by default to accept TCP
4302 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4303 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4304 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4307 hostname = g_strdup(sip->sipdomain);
4308 create_connection(sip, hostname, 0);
4309 return;
4313 /* Try to resolve next service */
4314 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4315 sip->service_data->transport,
4316 sip->sipdomain,
4317 srvresolved, sip);
4320 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4322 struct sipe_account_data *sip = data;
4324 sip->srv_query_data = NULL;
4326 /* find the host to connect to */
4327 if (results) {
4328 gchar *hostname = g_strdup(resp->hostname);
4329 int port = resp->port;
4330 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4331 hostname, port);
4332 g_free(resp);
4334 sip->transport = sip->service_data->type;
4336 create_connection(sip, hostname, port);
4337 } else {
4338 resolve_next_service(sip, NULL);
4342 static void sipe_login(PurpleAccount *account)
4344 PurpleConnection *gc;
4345 struct sipe_account_data *sip;
4346 gchar **signinname_login, **userserver, **domain_user;
4347 const char *transport;
4349 const char *username = purple_account_get_username(account);
4350 gc = purple_account_get_connection(account);
4352 if (strpbrk(username, " \t\v\r\n") != NULL) {
4353 gc->wants_to_die = TRUE;
4354 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4355 return;
4358 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4359 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4360 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4361 sip->gc = gc;
4362 sip->account = account;
4363 sip->registerexpire = 900;
4364 sip->reregister_set = FALSE;
4365 sip->reauthenticate_set = FALSE;
4366 sip->subscribed = FALSE;
4367 sip->subscribed_buddies = FALSE;
4369 signinname_login = g_strsplit(username, ",", 2);
4371 userserver = g_strsplit(signinname_login[0], "@", 2);
4372 purple_connection_set_display_name(gc, userserver[0]);
4373 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4374 sip->sipdomain = g_strdup(userserver[1]);
4376 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4377 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : "";
4378 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4380 sip->password = g_strdup(purple_connection_get_password(gc));
4382 g_strfreev(userserver);
4383 g_strfreev(domain_user);
4384 g_strfreev(signinname_login);
4386 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4388 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4390 /* TODO: Set the status correctly. */
4391 sip->status = g_strdup("available");
4393 transport = purple_account_get_string(account, "transport", "auto");
4394 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4395 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4396 SIPE_TRANSPORT_UDP;
4398 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4399 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4400 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4401 } else if (strcmp(transport, "auto") == 0) {
4402 sip->auto_transport = TRUE;
4403 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4404 } else if (strcmp(transport, "tls") == 0) {
4405 resolve_next_service(sip, service_tls);
4406 } else if (strcmp(transport, "tcp") == 0) {
4407 resolve_next_service(sip, service_tcp);
4408 } else {
4409 resolve_next_service(sip, service_udp);
4413 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4415 connection_free_all(sip);
4417 if (sip->query_data != NULL)
4418 purple_dnsquery_destroy(sip->query_data);
4419 sip->query_data == NULL;
4421 if (sip->srv_query_data != NULL)
4422 purple_srv_cancel(sip->srv_query_data);
4423 sip->srv_query_data = NULL;
4425 if (sip->listen_data != NULL)
4426 purple_network_listen_cancel(sip->listen_data);
4427 sip->listen_data = NULL;
4429 if (sip->gsc != NULL)
4430 purple_ssl_close(sip->gsc);
4431 sip->gsc = NULL;
4433 sipe_auth_free(&sip->registrar);
4434 sipe_auth_free(&sip->proxy);
4436 if (sip->txbuf)
4437 purple_circ_buffer_destroy(sip->txbuf);
4438 sip->txbuf = NULL;
4440 g_free(sip->realhostname);
4441 sip->realhostname = NULL;
4443 if (sip->listenpa)
4444 purple_input_remove(sip->listenpa);
4445 sip->listenpa = 0;
4446 if (sip->tx_handler)
4447 purple_input_remove(sip->tx_handler);
4448 sip->tx_handler = 0;
4449 if (sip->resendtimeout)
4450 purple_timeout_remove(sip->resendtimeout);
4451 sip->resendtimeout = 0;
4452 if (sip->timeouts) {
4453 GSList *entry = sip->timeouts;
4454 while (entry) {
4455 struct scheduled_action *sched_action = entry->data;
4456 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4457 purple_timeout_remove(sched_action->timeout_handler);
4458 g_free(sched_action->payload);
4459 g_free(sched_action->name);
4460 g_free(sched_action);
4461 entry = entry->next;
4464 g_slist_free(sip->timeouts);
4466 if (sip->contact)
4467 g_free(sip->contact);
4468 sip->contact = NULL;
4470 sip->fd = -1;
4471 sip->processing_input = FALSE;
4474 static void sipe_close(PurpleConnection *gc)
4476 struct sipe_account_data *sip = gc->proto_data;
4478 if (sip) {
4479 /* leave all conversations */
4480 im_session_close_all(sip);
4482 /* unregister */
4483 do_register_exp(sip, 0);
4485 sipe_connection_cleanup(sip);
4486 g_free(sip->sipdomain);
4487 g_free(sip->username);
4488 g_free(sip->password);
4490 g_free(gc->proto_data);
4491 gc->proto_data = NULL;
4494 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4496 PurpleAccount *acct = purple_connection_get_account(gc);
4497 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4498 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4499 if (conv == NULL)
4500 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4501 purple_conversation_present(conv);
4502 g_free(id);
4505 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4508 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4509 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4512 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4514 PurpleNotifySearchResults *results;
4515 PurpleNotifySearchColumn *column;
4516 xmlnode *searchResults;
4517 xmlnode *mrow;
4518 int match_count = 0;
4519 gboolean more = FALSE;
4520 gchar *secondary;
4522 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4523 if (!searchResults) {
4524 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4525 return FALSE;
4528 results = purple_notify_searchresults_new();
4530 if (results == NULL) {
4531 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4532 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4534 xmlnode_free(searchResults);
4535 return FALSE;
4538 column = purple_notify_searchresults_column_new(_("User Name"));
4539 purple_notify_searchresults_column_add(results, column);
4541 column = purple_notify_searchresults_column_new(_("Name"));
4542 purple_notify_searchresults_column_add(results, column);
4544 column = purple_notify_searchresults_column_new(_("Company"));
4545 purple_notify_searchresults_column_add(results, column);
4547 column = purple_notify_searchresults_column_new(_("Country"));
4548 purple_notify_searchresults_column_add(results, column);
4550 column = purple_notify_searchresults_column_new(_("Email"));
4551 purple_notify_searchresults_column_add(results, column);
4553 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4554 GList *row = NULL;
4556 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4557 row = g_list_append(row, g_strdup(uri_parts[1]));
4558 g_strfreev(uri_parts);
4560 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4561 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4562 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4563 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4565 purple_notify_searchresults_row_add(results, row);
4566 match_count++;
4569 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4570 char *data = xmlnode_get_data_unescaped(mrow);
4571 more = (g_strcasecmp(data, "true") == 0);
4572 g_free(data);
4575 secondary = g_strdup_printf(
4576 dngettext(GETTEXT_PACKAGE,
4577 "Found %d contact%s:",
4578 "Found %d contacts%s:", match_count),
4579 match_count, more ? _(" (more matched your query)") : "");
4581 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4582 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4583 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4585 g_free(secondary);
4586 xmlnode_free(searchResults);
4587 return TRUE;
4590 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4592 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4593 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4594 unsigned i = 0;
4596 do {
4597 PurpleRequestField *field = entries->data;
4598 const char *id = purple_request_field_get_id(field);
4599 const char *value = purple_request_field_string_get_value(field);
4601 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
4603 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
4604 } while ((entries = g_list_next(entries)) != NULL);
4605 attrs[i] = NULL;
4607 if (i > 0) {
4608 gchar *query = g_strjoinv(NULL, attrs);
4609 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
4610 send_soap_request_with_cb(gc->proto_data, body,
4611 (TransCallback) process_search_contact_response, NULL);
4612 g_free(body);
4613 g_free(query);
4616 g_strfreev(attrs);
4619 static void sipe_show_find_contact(PurplePluginAction *action)
4621 PurpleConnection *gc = (PurpleConnection *) action->context;
4622 PurpleRequestFields *fields;
4623 PurpleRequestFieldGroup *group;
4624 PurpleRequestField *field;
4626 fields = purple_request_fields_new();
4627 group = purple_request_field_group_new(NULL);
4628 purple_request_fields_add_group(fields, group);
4630 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4631 purple_request_field_group_add_field(group, field);
4632 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4633 purple_request_field_group_add_field(group, field);
4634 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4635 purple_request_field_group_add_field(group, field);
4636 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4637 purple_request_field_group_add_field(group, field);
4639 purple_request_fields(gc,
4640 _("Search"),
4641 _("Search for a Contact"),
4642 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4643 fields,
4644 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4645 _("_Cancel"), NULL,
4646 purple_connection_get_account(gc), NULL, NULL, gc);
4649 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4651 GList *menu = NULL;
4652 PurplePluginAction *act;
4654 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4655 menu = g_list_prepend(menu, act);
4657 menu = g_list_reverse(menu);
4659 return menu;
4662 static void dummy_permit_deny(PurpleConnection *gc)
4666 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4668 return TRUE;
4672 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4674 return TRUE;
4678 static void sipe_plugin_destroy(PurplePlugin *plugin)
4682 static char *sipe_status_text(PurpleBuddy *buddy)
4684 struct sipe_account_data *sip;
4685 struct sipe_buddy *sbuddy;
4686 char *text = NULL;
4688 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4689 if (sip) //happens on pidgin exit
4691 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4692 if (sbuddy && sbuddy->annotation)
4694 text = g_strdup(sbuddy->annotation);
4698 return text;
4701 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4703 const PurplePresence *presence = purple_buddy_get_presence(buddy);
4704 const PurpleStatus *status = purple_presence_get_active_status(presence);
4705 struct sipe_account_data *sip;
4706 struct sipe_buddy *sbuddy;
4707 const char *email;
4708 char *annotation = NULL;
4709 char *device_name = NULL;
4711 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4712 if (sip) //happens on pidgin exit
4714 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4715 if (sbuddy)
4717 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
4718 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
4722 //Layout
4723 if (purple_presence_is_online(presence))
4725 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
4728 if (annotation)
4730 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
4731 g_free(annotation);
4734 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4735 if (email)
4737 purple_notify_user_info_add_pair(user_info, _("Email"), email);
4740 if (device_name)
4742 purple_notify_user_info_add_pair(user_info, _("Device"), device_name);
4746 static GHashTable *
4747 sipe_get_account_text_table(PurpleAccount *account)
4749 GHashTable *table;
4750 table = g_hash_table_new(g_str_hash, g_str_equal);
4751 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
4752 return table;
4755 static PurpleBuddy *
4756 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
4758 PurpleBuddy *clone;
4759 const gchar *server_alias, *email;
4760 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
4762 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
4764 purple_blist_add_buddy(clone, NULL, group, NULL);
4766 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
4767 if (server_alias) {
4768 purple_blist_server_alias_buddy(clone, server_alias);
4771 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4772 if (email) {
4773 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
4776 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
4777 //for UI to update;
4778 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
4779 return clone;
4782 static void
4783 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
4785 PurpleBuddy *buddy, *b;
4786 PurpleConnection *gc;
4787 PurpleGroup * group = purple_find_group(group_name);
4789 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
4791 buddy = (PurpleBuddy *)node;
4793 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
4794 gc = purple_account_get_connection(buddy->account);
4796 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
4797 if (!b){
4798 b = purple_blist_add_buddy_clone(group, buddy);
4801 sipe_group_buddy(gc, buddy->name, NULL, group_name);
4805 * A menu which appear when right-clicking on buddy in contact list.
4807 static GList *
4808 sipe_buddy_menu(PurpleBuddy *buddy)
4810 PurpleBlistNode *g_node;
4811 PurpleGroup *group, *gr_parent;
4812 PurpleMenuAction *act;
4813 GList *menu = NULL;
4814 GList *menu_groups = NULL;
4816 gr_parent = purple_buddy_get_group(buddy);
4817 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
4818 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
4819 continue;
4821 group = (PurpleGroup *)g_node;
4822 if (group == gr_parent)
4823 continue;
4825 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
4826 continue;
4828 act = purple_menu_action_new(purple_group_get_name(group),
4829 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
4830 group->name, NULL);
4831 menu_groups = g_list_prepend(menu_groups, act);
4833 menu_groups = g_list_reverse(menu_groups);
4835 act = purple_menu_action_new(_("Copy to"),
4836 NULL,
4837 NULL, menu_groups);
4838 menu = g_list_prepend(menu, act);
4839 menu = g_list_reverse(menu);
4841 return menu;
4844 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
4845 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4846 return sipe_buddy_menu((PurpleBuddy *) node);
4847 } else {
4848 return NULL;
4852 static PurplePlugin *my_protocol = NULL;
4854 static PurplePluginProtocolInfo prpl_info =
4857 NULL, /* user_splits */
4858 NULL, /* protocol_options */
4859 NO_BUDDY_ICONS, /* icon_spec */
4860 sipe_list_icon, /* list_icon */
4861 NULL, /* list_emblems */
4862 sipe_status_text, /* status_text */
4863 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
4864 sipe_status_types, /* away_states */
4865 sipe_blist_node_menu, /* blist_node_menu */
4866 NULL, /* chat_info */
4867 NULL, /* chat_info_defaults */
4868 sipe_login, /* login */
4869 sipe_close, /* close */
4870 sipe_im_send, /* send_im */
4871 NULL, /* set_info */ // TODO maybe
4872 sipe_send_typing, /* send_typing */
4873 NULL, /* get_info */ // TODO maybe
4874 sipe_set_status, /* set_status */
4875 NULL, /* set_idle */
4876 NULL, /* change_passwd */
4877 sipe_add_buddy, /* add_buddy */
4878 NULL, /* add_buddies */
4879 sipe_remove_buddy, /* remove_buddy */
4880 NULL, /* remove_buddies */
4881 sipe_add_permit, /* add_permit */
4882 sipe_add_deny, /* add_deny */
4883 sipe_add_deny, /* rem_permit */
4884 sipe_add_permit, /* rem_deny */
4885 dummy_permit_deny, /* set_permit_deny */
4886 NULL, /* join_chat */
4887 NULL, /* reject_chat */
4888 NULL, /* get_chat_name */
4889 NULL, /* chat_invite */
4890 NULL, /* chat_leave */
4891 NULL, /* chat_whisper */
4892 NULL, /* chat_send */
4893 sipe_keep_alive, /* keepalive */
4894 NULL, /* register_user */
4895 NULL, /* get_cb_info */ // deprecated
4896 NULL, /* get_cb_away */ // deprecated
4897 sipe_alias_buddy, /* alias_buddy */
4898 sipe_group_buddy, /* group_buddy */
4899 sipe_rename_group, /* rename_group */
4900 NULL, /* buddy_free */
4901 sipe_convo_closed, /* convo_closed */
4902 purple_normalize_nocase, /* normalize */
4903 NULL, /* set_buddy_icon */
4904 sipe_remove_group, /* remove_group */
4905 NULL, /* get_cb_real_name */ // TODO?
4906 NULL, /* set_chat_topic */
4907 NULL, /* find_blist_chat */
4908 NULL, /* roomlist_get_list */
4909 NULL, /* roomlist_cancel */
4910 NULL, /* roomlist_expand_category */
4911 NULL, /* can_receive_file */
4912 NULL, /* send_file */
4913 NULL, /* new_xfer */
4914 NULL, /* offline_message */
4915 NULL, /* whiteboard_prpl_ops */
4916 sipe_send_raw, /* send_raw */
4917 NULL, /* roomlist_room_serialize */
4918 NULL, /* unregister_user */
4919 NULL, /* send_attention */
4920 NULL, /* get_attention_types */
4922 sizeof(PurplePluginProtocolInfo), /* struct_size */
4923 sipe_get_account_text_table, /* get_account_text_table */
4927 static PurplePluginInfo info = {
4928 PURPLE_PLUGIN_MAGIC,
4929 PURPLE_MAJOR_VERSION,
4930 PURPLE_MINOR_VERSION,
4931 PURPLE_PLUGIN_PROTOCOL, /**< type */
4932 NULL, /**< ui_requirement */
4933 0, /**< flags */
4934 NULL, /**< dependencies */
4935 PURPLE_PRIORITY_DEFAULT, /**< priority */
4936 "prpl-sipe", /**< id */
4937 "Microsoft LCS/OCS", /**< name */
4938 VERSION, /**< version */
4939 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
4940 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
4941 "Anibal Avelar <avelar@gmail.com>, " /**< author */
4942 "Gabriel Burt <gburt@novell.com>", /**< author */
4943 PURPLE_WEBSITE, /**< homepage */
4944 sipe_plugin_load, /**< load */
4945 sipe_plugin_unload, /**< unload */
4946 sipe_plugin_destroy, /**< destroy */
4947 NULL, /**< ui_info */
4948 &prpl_info, /**< extra_info */
4949 NULL,
4950 sipe_actions,
4951 NULL,
4952 NULL,
4953 NULL,
4954 NULL
4957 static void init_plugin(PurplePlugin *plugin)
4959 PurpleAccountUserSplit *split;
4960 PurpleAccountOption *option;
4962 #ifdef ENABLE_NLS
4963 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
4964 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
4965 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
4966 #endif
4968 purple_plugin_register(plugin);
4970 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
4971 purple_account_user_split_set_reverse(split, FALSE);
4972 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
4974 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
4975 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4976 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
4977 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4979 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
4980 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4981 // Translators: noun (networking port)
4982 option = purple_account_option_int_new(_("Port"), "port", 5061);
4983 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4985 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
4986 purple_account_option_add_list_item(option, _("Auto"), "auto");
4987 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
4988 purple_account_option_add_list_item(option, _("TCP"), "tcp");
4989 purple_account_option_add_list_item(option, _("UDP"), "udp");
4990 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4992 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
4993 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
4995 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
4996 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4998 // TODO commented out so won't show in the preferences until we fix krb message signing
4999 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5000 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5002 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5003 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5004 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5007 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5008 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5009 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5010 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5011 my_protocol = plugin;
5014 /* I had to redefined the function for it load, but works */
5015 gboolean purple_init_plugin(PurplePlugin *plugin){
5016 plugin->info = &(info);
5017 init_plugin((plugin));
5018 sipe_plugin_load((plugin));
5019 return purple_plugin_register(plugin);