Merge branch 'mob' of git+ssh://aavelar@repo.or.cz/srv/git/siplcs into mob
[siplcs.git] / src / sipe.c
blob4dbe425dde697a07b906c53ec035a7cd2f041309
1 /**
2 * @file sipe.c
4 * pidgin-sipe
5 *
6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
11 * ***
12 * Thanks to Google's Summer of Code Program and the helpful mentors
13 * ***
15 * Session-based SIP MESSAGE documentation:
16 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #ifndef _WIN32
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <netinet/in.h>
38 #include <net/if.h>
39 #ifdef ENABLE_NLS
40 # include <libintl.h>
41 # define _(String) ((const char *) gettext (String))
42 #else
43 # define _(String) ((const char *) (String))
44 #endif /* ENABLE_NLS */
45 #else
46 #ifdef _DLL
47 #define _WS2TCPIP_H_
48 #define _WINSOCK2API_
49 #define _LIBC_INTERNAL_
50 #endif /* _DLL */
52 #include "internal.h"
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <string.h>
59 #include <glib.h>
62 #include "accountopt.h"
63 #include "blist.h"
64 #include "conversation.h"
65 #include "dnsquery.h"
66 #include "debug.h"
67 #include "notify.h"
68 #include "privacy.h"
69 #include "prpl.h"
70 #include "plugin.h"
71 #include "util.h"
72 #include "version.h"
73 #include "network.h"
74 #include "xmlnode.h"
75 #include "mime.h"
77 #include "sipe.h"
78 #include "sip-ntlm.h"
79 #ifdef USE_KERBEROS
80 #include "sipkrb5.h"
81 #endif /*USE_KERBEROS*/
83 #include "sipmsg.h"
84 #include "sipe-sign.h"
85 #include "dnssrv.h"
86 #include "request.h"
88 /* Keep in sync with sipe_transport_type! */
89 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
90 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
92 /* Status identifiers (see also: sipe_status_types()) */
93 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
94 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
95 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
96 /* PURPLE_STATUS_UNAVAILABLE: */
97 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
98 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
99 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
100 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
101 /* PURPLE_STATUS_AWAY: */
102 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
103 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
104 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
105 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
106 /* ??? PURPLE_STATUS_MOBILE */
107 /* ??? PURPLE_STATUS_TUNE */
109 static char *gentag()
111 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
114 static gchar *get_epid(struct sipe_account_data *sip)
116 if (!sip->epid) {
117 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
119 return g_strdup(sip->epid);
122 static char *genbranch()
124 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
125 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
126 rand() & 0xFFFF, rand() & 0xFFFF);
129 static char *gencallid()
131 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
132 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
133 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
134 rand() & 0xFFFF, rand() & 0xFFFF);
137 static gchar *find_tag(const gchar *hdr)
139 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
140 if (!tag) {
141 // In case it's at the end and there's no trailing ;
142 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
144 return tag;
148 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
150 return "sipe";
153 static void sipe_plugin_destroy(PurplePlugin *plugin);
155 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
157 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
158 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
159 gpointer data);
161 static void sipe_close(PurpleConnection *gc);
163 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name);
164 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip);
165 static void send_presence_status(struct sipe_account_data *sip);
167 static void sendout_pkt(PurpleConnection *gc, const char *buf);
169 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, gchar *timeout)
171 if (timeout != NULL) {
172 sscanf(timeout, "%u", &sip->keepalive_timeout);
173 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
174 sip->keepalive_timeout);
176 g_free(timeout);
179 static void sipe_keep_alive(PurpleConnection *gc)
181 struct sipe_account_data *sip = gc->proto_data;
182 if (sip->transport == SIPE_TRANSPORT_UDP) {
183 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
184 gchar buf[2] = {0, 0};
185 purple_debug_info("sipe", "sending keep alive\n");
186 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
187 } else {
188 time_t now = time(NULL);
189 if ((sip->keepalive_timeout > 0) &&
190 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
191 #if PURPLE_VERSION_CHECK(2,4,0)
192 && ((now - gc->last_received) >= sip->keepalive_timeout)
193 #endif
195 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
196 sendout_pkt(gc, "\r\n\r\n");
197 sip->last_keepalive = now;
202 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
204 struct sip_connection *ret = NULL;
205 GSList *entry = sip->openconns;
206 while (entry) {
207 ret = entry->data;
208 if (ret->fd == fd) return ret;
209 entry = entry->next;
211 return NULL;
214 static void sipe_auth_free(struct sip_auth *auth)
216 g_free(auth->nonce);
217 auth->nonce = NULL;
218 g_free(auth->opaque);
219 auth->opaque = NULL;
220 g_free(auth->realm);
221 auth->realm = NULL;
222 g_free(auth->target);
223 auth->target = NULL;
224 g_free(auth->digest_session_key);
225 auth->digest_session_key = NULL;
226 g_free(auth->ntlm_key);
227 auth->ntlm_key = NULL;
228 auth->type = AUTH_TYPE_UNSET;
229 auth->retries = 0;
230 auth->expires = 0;
233 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
235 struct sip_connection *ret = g_new0(struct sip_connection, 1);
236 ret->fd = fd;
237 sip->openconns = g_slist_append(sip->openconns, ret);
238 return ret;
241 static void connection_remove(struct sipe_account_data *sip, int fd)
243 struct sip_connection *conn = connection_find(sip, fd);
244 if (conn) {
245 sip->openconns = g_slist_remove(sip->openconns, conn);
246 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
247 g_free(conn->inbuf);
248 g_free(conn);
252 static void connection_free_all(struct sipe_account_data *sip)
254 struct sip_connection *ret = NULL;
255 GSList *entry = sip->openconns;
256 while (entry) {
257 ret = entry->data;
258 connection_remove(sip, ret->fd);
259 entry = sip->openconns;
263 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
265 const gchar *method = msg->method;
266 const gchar *target = msg->target;
267 gchar noncecount[9];
268 gchar *response;
269 gchar *ret;
270 gchar *tmp = NULL;
271 const char *authdomain = sip->authdomain;
272 const char *authuser = sip->authuser;
273 //const char *krb5_realm;
274 const char *host;
275 //gchar *krb5_token = NULL;
277 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
278 // and do error checking
280 // KRB realm should always be uppercase
281 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
283 if (sip->realhostname) {
284 host = sip->realhostname;
285 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
286 host = purple_account_get_string(sip->account, "proxy", "");
287 } else {
288 host = sip->sipdomain;
291 /*gboolean new_auth = krb5_auth.gss_context == NULL;
292 if (new_auth) {
293 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
296 if (new_auth || force_reauth) {
297 krb5_token = krb5_auth.base64_token;
300 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
301 krb5_token = krb5_auth.base64_token;*/
303 if (!authdomain) {
304 authdomain = "";
307 if (!authuser || strlen(authuser) < 1) {
308 authuser = sip->username;
311 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
312 sprintf(noncecount, "%08d", auth->nc++);
313 response = purple_cipher_http_digest_calculate_response(
314 "md5", method, target, NULL, NULL,
315 auth->nonce, noncecount, NULL, auth->digest_session_key);
316 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
318 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);
319 g_free(response);
320 return ret;
321 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
322 // If we have a signature for the message, include that
323 if (msg->signature) {
324 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);
325 return tmp;
328 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
329 const gchar *ntlm_key;
330 gchar *gssapi_data;
331 #if GLIB_CHECK_VERSION(2,8,0)
332 const gchar * hostname = g_get_host_name();
333 #else
334 static char hostname[256];
335 int ret = gethostname(hostname, sizeof(hostname));
336 hostname[sizeof(hostname) - 1] = '\0';
337 if (ret == -1 || hostname[0] == '\0') {
338 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
339 g_strerror(errno);
340 strcpy(hostname, "localhost");
342 #endif
343 /*const gchar * hostname = purple_get_host_name();*/
345 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
346 auth->ntlm_key = (gchar *)ntlm_key;
347 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
348 g_free(gssapi_data);
349 return tmp;
352 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
353 return tmp;
354 } else if (auth->type == AUTH_TYPE_KERBEROS) {
355 /* Kerberos */
356 if (auth->nc == 3) {
357 /*if (new_auth || force_reauth) {
358 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
359 if (auth->opaque) {
360 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);
361 } else {
362 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
364 } else {
365 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
366 gchar * mic = "MICTODO";
367 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
368 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
369 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
370 //auth->opaque ? auth->opaque : "", auth->target);
371 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
372 //g_free(mic);
374 return tmp;
376 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
379 sprintf(noncecount, "%08d", auth->nc++);
380 response = purple_cipher_http_digest_calculate_response(
381 "md5", method, target, NULL, NULL,
382 auth->nonce, noncecount, NULL, auth->digest_session_key);
383 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
385 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);
386 g_free(response);
387 return ret;
390 static char *parse_attribute(const char *attrname, const char *source)
392 const char *tmp, *tmp2;
393 char *retval = NULL;
394 int len = strlen(attrname);
396 if (!strncmp(source, attrname, len)) {
397 tmp = source + len;
398 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
399 if (tmp2)
400 retval = g_strndup(tmp, tmp2 - tmp);
401 else
402 retval = g_strdup(tmp);
405 return retval;
408 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
410 int i = 0;
411 const char *authuser;
412 char *tmp;
413 gchar **parts;
414 //const char *krb5_realm;
415 //const char *host;
417 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
418 // and do error checking
420 // KRB realm should always be uppercase
421 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
423 if (sip->realhostname) {
424 host = sip->realhostname;
425 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
426 host = purple_account_get_string(sip->account, "proxy", "");
427 } else {
428 host = sip->sipdomain;
431 authuser = sip->authuser;
433 if (!authuser || strlen(authuser) < 1) {
434 authuser = sip->username;
437 if (!hdr) {
438 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
439 return;
442 if (!g_strncasecmp(hdr, "NTLM", 4)) {
443 auth->type = AUTH_TYPE_NTLM;
444 parts = g_strsplit(hdr+5, "\", ", 0);
445 i = 0;
446 while (parts[i]) {
447 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
448 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
449 g_free(auth->nonce);
450 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
451 g_free(tmp);
453 if ((tmp = parse_attribute("targetname=\"",
454 parts[i]))) {
455 g_free(auth->target);
456 auth->target = tmp;
458 else if ((tmp = parse_attribute("realm=\"",
459 parts[i]))) {
460 g_free(auth->realm);
461 auth->realm = tmp;
463 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
464 g_free(auth->opaque);
465 auth->opaque = tmp;
467 i++;
469 g_strfreev(parts);
470 auth->nc = 1;
471 if (!strstr(hdr, "gssapi-data")) {
472 auth->nc = 1;
473 } else {
474 auth->nc = 3;
476 return;
479 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
480 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
481 auth->type = AUTH_TYPE_KERBEROS;
482 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
483 parts = g_strsplit(hdr+9, "\", ", 0);
484 i = 0;
485 while (parts[i]) {
486 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
487 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
488 /*if (krb5_auth.gss_context == NULL) {
489 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
491 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
492 g_free(tmp);
494 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
495 g_free(auth->target);
496 auth->target = tmp;
497 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
498 g_free(auth->realm);
499 auth->realm = tmp;
500 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
501 g_free(auth->opaque);
502 auth->opaque = tmp;
504 i++;
506 g_strfreev(parts);
507 auth->nc = 3;
508 return;
511 auth->type = AUTH_TYPE_DIGEST;
512 parts = g_strsplit(hdr, " ", 0);
513 while (parts[i]) {
514 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
515 g_free(auth->nonce);
516 auth->nonce = tmp;
518 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
519 g_free(auth->realm);
520 auth->realm = tmp;
522 i++;
524 g_strfreev(parts);
526 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
527 if (auth->realm) {
528 g_free(auth->digest_session_key);
529 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
530 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
532 auth->nc = 1;
536 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
538 PurpleConnection *gc = data;
539 struct sipe_account_data *sip = gc->proto_data;
540 gsize max_write;
541 gssize written;
543 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
545 if (max_write == 0) {
546 if (sip->tx_handler != 0){
547 purple_input_remove(sip->tx_handler);
548 sip->tx_handler = 0;
550 return;
553 written = write(sip->fd, sip->txbuf->outptr, max_write);
555 if (written < 0 && errno == EAGAIN)
556 written = 0;
557 else if (written <= 0) {
558 /*TODO: do we really want to disconnect on a failure to write?*/
559 purple_connection_error(gc, _("Could not write"));
560 return;
563 purple_circ_buffer_mark_read(sip->txbuf, written);
566 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
568 PurpleConnection *gc = data;
569 struct sipe_account_data *sip = gc->proto_data;
570 gsize max_write;
571 gssize written;
573 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
575 if (max_write == 0) {
576 if (sip->tx_handler != 0) {
577 purple_input_remove(sip->tx_handler);
578 sip->tx_handler = 0;
579 return;
583 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
585 if (written < 0 && errno == EAGAIN)
586 written = 0;
587 else if (written <= 0) {
588 /*TODO: do we really want to disconnect on a failure to write?*/
589 purple_connection_error(gc, _("Could not write"));
590 return;
593 purple_circ_buffer_mark_read(sip->txbuf, written);
596 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
598 static void send_later_cb(gpointer data, gint source, const gchar *error)
600 PurpleConnection *gc = data;
601 struct sipe_account_data *sip;
602 struct sip_connection *conn;
604 if (!PURPLE_CONNECTION_IS_VALID(gc))
606 if (source >= 0)
607 close(source);
608 return;
611 if (source < 0) {
612 purple_connection_error(gc, _("Could not connect"));
613 return;
616 sip = gc->proto_data;
617 sip->fd = source;
618 sip->connecting = FALSE;
619 sip->last_keepalive = time(NULL);
621 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
623 /* If there is more to write now, we need to register a handler */
624 if (sip->txbuf->bufused > 0)
625 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
627 conn = connection_create(sip, source);
628 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
631 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
633 struct sipe_account_data *sip;
634 struct sip_connection *conn;
636 if (!PURPLE_CONNECTION_IS_VALID(gc))
638 if (gsc) purple_ssl_close(gsc);
639 return NULL;
642 sip = gc->proto_data;
643 sip->fd = gsc->fd;
644 sip->gsc = gsc;
645 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
646 sip->connecting = FALSE;
647 sip->last_keepalive = time(NULL);
649 conn = connection_create(sip, gsc->fd);
651 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
653 return sip;
656 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
658 PurpleConnection *gc = data;
659 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
660 if (sip == NULL) return;
662 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
664 /* If there is more to write now */
665 if (sip->txbuf->bufused > 0) {
666 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
671 static void sendlater(PurpleConnection *gc, const char *buf)
673 struct sipe_account_data *sip = gc->proto_data;
675 if (!sip->connecting) {
676 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
677 if (sip->transport == SIPE_TRANSPORT_TLS){
678 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
679 } else {
680 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
681 purple_connection_error(gc, _("Couldn't create socket"));
684 sip->connecting = TRUE;
687 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
688 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
690 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
693 static void sendout_pkt(PurpleConnection *gc, const char *buf)
695 struct sipe_account_data *sip = gc->proto_data;
696 time_t currtime = time(NULL);
697 int writelen = strlen(buf);
699 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
700 if (sip->transport == SIPE_TRANSPORT_UDP) {
701 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
702 purple_debug_info("sipe", "could not send packet\n");
704 } else {
705 int ret;
706 if (sip->fd < 0) {
707 sendlater(gc, buf);
708 return;
711 if (sip->tx_handler) {
712 ret = -1;
713 errno = EAGAIN;
714 } else{
715 if (sip->gsc){
716 ret = purple_ssl_write(sip->gsc, buf, writelen);
717 }else{
718 ret = write(sip->fd, buf, writelen);
722 if (ret < 0 && errno == EAGAIN)
723 ret = 0;
724 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
725 sendlater(gc, buf);
726 return;
729 if (ret < writelen) {
730 if (!sip->tx_handler){
731 if (sip->gsc){
732 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
734 else{
735 sip->tx_handler = purple_input_add(sip->fd,
736 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
737 gc);
741 /* XXX: is it OK to do this? You might get part of a request sent
742 with part of another. */
743 if (sip->txbuf->bufused > 0)
744 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
746 purple_circ_buffer_append(sip->txbuf, buf + ret,
747 writelen - ret);
752 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
754 sendout_pkt(gc, buf);
755 return len;
758 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
760 GSList *tmp = msg->headers;
761 gchar *name;
762 gchar *value;
763 GString *outstr = g_string_new("");
764 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
765 while (tmp) {
766 name = ((struct siphdrelement*) (tmp->data))->name;
767 value = ((struct siphdrelement*) (tmp->data))->value;
768 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
769 tmp = g_slist_next(tmp);
771 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
772 sendout_pkt(sip->gc, outstr->str);
773 g_string_free(outstr, TRUE);
776 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
778 gchar * buf;
779 if (sip->registrar.ntlm_key) {
780 struct sipmsg_breakdown msgbd;
781 gchar *signature_input_str;
782 msgbd.msg = msg;
783 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
784 msgbd.rand = g_strdup_printf("%08x", g_random_int());
785 sip->registrar.ntlm_num++;
786 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
787 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
788 if (signature_input_str != NULL) {
789 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
790 msg->rand = g_strdup(msgbd.rand);
791 msg->num = g_strdup(msgbd.num);
792 g_free(signature_input_str);
794 sipmsg_breakdown_free(&msgbd);
797 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
798 buf = auth_header(sip, &sip->registrar, msg);
799 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
800 sipmsg_add_header(msg, "Authorization", buf);
801 } else {
802 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
804 g_free(buf);
805 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS")) {
806 sip->registrar.nc = 3;
807 sip->registrar.type = AUTH_TYPE_NTLM;
809 buf = auth_header(sip, &sip->registrar, msg);
810 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
811 g_free(buf);
812 } else {
813 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
817 static char *get_contact(struct sipe_account_data *sip)
819 return g_strdup(sip->contact);
823 * unused. Needed?
824 static char *get_contact_service(struct sipe_account_data *sip)
826 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()));
827 //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);
831 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
832 const char *text, const char *body)
834 gchar *name;
835 gchar *value;
836 GString *outstr = g_string_new("");
837 struct sipe_account_data *sip = gc->proto_data;
838 gchar *contact;
839 GSList *tmp;
841 sipmsg_remove_header(msg, "ms-user-data");
843 contact = get_contact(sip);
844 sipmsg_remove_header(msg, "Contact");
845 sipmsg_add_header(msg, "Contact", contact);
846 g_free(contact);
848 /* When sending the acknowlegements and errors, the content length from the original
849 message is still here, but there is no body; we need to make sure we're sending the
850 correct content length */
851 sipmsg_remove_header(msg, "Content-Length");
852 if (body) {
853 gchar len[12];
854 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
855 sipmsg_add_header(msg, "Content-Length", len);
856 } else {
857 sipmsg_remove_header(msg, "Content-Type");
858 sipmsg_add_header(msg, "Content-Length", "0");
861 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
862 //gchar * mic = "MICTODO";
863 msg->response = code;
865 sipmsg_remove_header(msg, "Authentication-Info");
866 sign_outgoing_message(msg, sip, msg->method);
868 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
869 tmp = msg->headers;
870 while (tmp) {
871 name = ((struct siphdrelement*) (tmp->data))->name;
872 value = ((struct siphdrelement*) (tmp->data))->value;
874 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
875 tmp = g_slist_next(tmp);
877 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
878 sendout_pkt(gc, outstr->str);
879 g_string_free(outstr, TRUE);
882 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
884 if (trans->msg) sipmsg_free(trans->msg);
885 sip->transactions = g_slist_remove(sip->transactions, trans);
886 g_free(trans);
889 static struct transaction *
890 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
892 struct transaction *trans = g_new0(struct transaction, 1);
893 trans->time = time(NULL);
894 trans->msg = (struct sipmsg *)msg;
895 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
896 trans->callback = callback;
897 sip->transactions = g_slist_append(sip->transactions, trans);
898 return trans;
901 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
903 struct transaction *trans;
904 GSList *transactions = sip->transactions;
905 gchar *cseq = sipmsg_find_header(msg, "CSeq");
907 while (transactions) {
908 trans = transactions->data;
909 if (!strcmp(trans->cseq, cseq)) {
910 return trans;
912 transactions = transactions->next;
915 return NULL;
918 static struct transaction *
919 send_sip_request(PurpleConnection *gc, const gchar *method,
920 const gchar *url, const gchar *to, const gchar *addheaders,
921 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
923 struct sipe_account_data *sip = gc->proto_data;
924 const char *addh = "";
925 char *buf;
926 struct sipmsg *msg;
927 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
928 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
929 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
930 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
931 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
932 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
933 gchar *route = strdup("");
934 gchar *epid = get_epid(sip); // TODO generate one per account/login
935 struct transaction *trans;
937 if (dialog && dialog->routes)
939 GSList *iter = dialog->routes;
941 while(iter)
943 char *tmp = route;
944 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
945 g_free(tmp);
946 iter = g_slist_next(iter);
950 if (!ourtag && !dialog) {
951 ourtag = gentag();
954 if (!strcmp(method, "REGISTER")) {
955 if (sip->regcallid) {
956 g_free(callid);
957 callid = g_strdup(sip->regcallid);
958 } else {
959 sip->regcallid = g_strdup(callid);
963 if (addheaders) addh = addheaders;
965 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
966 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
967 "From: <sip:%s>%s%s;epid=%s\r\n"
968 "To: <%s>%s%s%s%s\r\n"
969 "Max-Forwards: 70\r\n"
970 "CSeq: %d %s\r\n"
971 "User-Agent: %s\r\n"
972 "Call-ID: %s\r\n"
973 "%s%s"
974 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
975 method,
976 dialog && dialog->request ? dialog->request : url,
977 TRANSPORT_DESCRIPTOR,
978 purple_network_get_my_ip(-1),
979 sip->listenport,
980 branch ? ";branch=" : "",
981 branch ? branch : "",
982 sip->username,
983 ourtag ? ";tag=" : "",
984 ourtag ? ourtag : "",
985 epid,
987 theirtag ? ";tag=" : "",
988 theirtag ? theirtag : "",
989 theirepid ? ";epid=" : "",
990 theirepid ? theirepid : "",
991 dialog ? ++dialog->cseq : ++sip->cseq,
992 method,
993 useragent,
994 callid,
995 route,
996 addh,
997 body ? strlen(body) : 0,
998 body ? body : "");
1001 //printf ("parsing msg buf:\n%s\n\n", buf);
1002 msg = sipmsg_parse_msg(buf);
1004 g_free(buf);
1005 g_free(ourtag);
1006 g_free(theirtag);
1007 g_free(theirepid);
1008 g_free(branch);
1009 g_free(callid);
1010 g_free(route);
1011 g_free(epid);
1013 sign_outgoing_message (msg, sip, method);
1015 buf = sipmsg_to_string (msg);
1017 /* add to ongoing transactions */
1018 trans = transactions_add_buf(sip, msg, tc);
1019 sendout_pkt(gc, buf);
1020 g_free(buf);
1022 return trans;
1025 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1027 gchar *from = g_strdup_printf("sip:%s", sip->username);
1028 gchar *contact = get_contact(sip);
1029 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1030 "Content-Type: application/SOAP+xml\r\n",contact);
1032 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1033 tr->payload = payload;
1035 g_free(from);
1036 g_free(contact);
1037 g_free(hdr);
1040 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1042 send_soap_request_with_cb(sip, body, NULL, NULL);
1045 static char *get_contact_register(struct sipe_account_data *sip)
1047 char *epid = get_epid(sip);
1048 char *uuid = generateUUIDfromEPID(epid);
1049 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1050 g_free(uuid);
1051 g_free(epid);
1052 return(buf);
1055 static void do_register_exp(struct sipe_account_data *sip, int expire)
1057 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1058 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1059 char *to = g_strdup_printf("sip:%s", sip->username);
1060 char *contact = get_contact_register(sip);
1061 char *hdr = g_strdup_printf("Contact: %s\r\n"
1062 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1063 "Event: registration\r\n"
1064 "Allow-Events: presence\r\n"
1065 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1066 "%s", contact, expires);
1067 g_free(contact);
1068 g_free(expires);
1070 sip->registerstatus = 1;
1072 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1073 process_register_response);
1075 g_free(hdr);
1076 g_free(uri);
1077 g_free(to);
1080 static void do_register_cb(struct sipe_account_data *sip)
1082 do_register_exp(sip, -1);
1083 sip->reregister_set = FALSE;
1086 static void do_register(struct sipe_account_data *sip)
1088 do_register_exp(sip, -1);
1092 * Returns URI from provided To or From header.
1094 * Needs to g_free() after use.
1096 * @return URI with sip: prefix
1098 static gchar *parse_from(const gchar *hdr)
1100 gchar *from;
1101 const gchar *tmp, *tmp2 = hdr;
1103 if (!hdr) return NULL;
1104 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1105 tmp = strchr(hdr, '<');
1107 /* i hate the different SIP UA behaviours... */
1108 if (tmp) { /* sip address in <...> */
1109 tmp2 = tmp + 1;
1110 tmp = strchr(tmp2, '>');
1111 if (tmp) {
1112 from = g_strndup(tmp2, tmp - tmp2);
1113 } else {
1114 purple_debug_info("sipe", "found < without > in From\n");
1115 return NULL;
1117 } else {
1118 tmp = strchr(tmp2, ';');
1119 if (tmp) {
1120 from = g_strndup(tmp2, tmp - tmp2);
1121 } else {
1122 from = g_strdup(tmp2);
1125 purple_debug_info("sipe", "got %s\n", from);
1126 return from;
1129 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1131 va_list args;
1132 xmlnode * node = NULL;
1133 const gchar * name;
1135 va_start(args, parent);
1136 while ((name = va_arg(args, const char *)) != NULL) {
1137 node = xmlnode_get_child(parent, name);
1138 if (node == NULL) return NULL;
1139 parent = node;
1141 va_end(args);
1143 return node;
1147 static void
1148 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1150 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1151 send_soap_request(sip, body);
1152 g_free(body);
1155 static void
1156 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1158 if (allow) {
1159 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1160 } else {
1161 purple_debug_info("sipe", "Blocking contact %s\n", who);
1164 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1167 static
1168 void sipe_auth_user_cb(void * data)
1170 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1171 if (!job) return;
1173 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1174 g_free(job);
1177 static
1178 void sipe_deny_user_cb(void * data)
1180 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1181 if (!job) return;
1183 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1184 g_free(job);
1187 static void
1188 sipe_add_permit(PurpleConnection *gc, const char *name)
1190 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1191 sipe_contact_allow_deny(sip, name, TRUE);
1194 static void
1195 sipe_add_deny(PurpleConnection *gc, const char *name)
1197 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1198 sipe_contact_allow_deny(sip, name, FALSE);
1201 /*static void
1202 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1204 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1205 sipe_contact_set_acl(sip, name, "");
1208 static void
1209 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1211 xmlnode *watchers;
1212 xmlnode *watcher;
1213 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1214 if (msg->response != 0 && msg->response != 200) return;
1216 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1218 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1219 if (!watchers) return;
1221 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1222 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1223 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1224 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1226 // TODO pull out optional displayName to pass as alias
1227 if (remote_user) {
1228 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1229 job->who = remote_user;
1230 job->sip = sip;
1231 purple_account_request_authorization(
1232 sip->account,
1233 remote_user,
1234 NULL, // id
1235 alias,
1236 NULL, // message
1237 on_list,
1238 sipe_auth_user_cb,
1239 sipe_deny_user_cb,
1240 (void *) job);
1245 xmlnode_free(watchers);
1246 return;
1249 static void
1250 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1252 PurpleGroup * purple_group = purple_find_group(group->name);
1253 if (!purple_group) {
1254 purple_group = purple_group_new(group->name);
1255 purple_blist_add_group(purple_group, NULL);
1258 if (purple_group) {
1259 group->purple_group = purple_group;
1260 sip->groups = g_slist_append(sip->groups, group);
1261 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1262 } else {
1263 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1267 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1269 struct sipe_group *group;
1270 GSList *entry;
1271 if (sip == NULL) {
1272 return NULL;
1275 entry = sip->groups;
1276 while (entry) {
1277 group = entry->data;
1278 if (group->id == id) {
1279 return group;
1281 entry = entry->next;
1283 return NULL;
1286 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1288 struct sipe_group *group;
1289 GSList *entry;
1290 if (sip == NULL) {
1291 return NULL;
1294 entry = sip->groups;
1295 while (entry) {
1296 group = entry->data;
1297 if (!strcmp(group->name, name)) {
1298 return group;
1300 entry = entry->next;
1302 return NULL;
1305 static void
1306 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1308 gchar *body;
1309 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1310 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1311 send_soap_request(sip, body);
1312 g_free(body);
1313 g_free(group->name);
1314 group->name = g_strdup(name);
1318 * Only appends if no such value already stored.
1319 * Like Set in Java.
1321 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1322 GSList * res = list;
1323 if (!g_slist_find_custom(list, data, func)) {
1324 res = g_slist_insert_sorted(list, data, func);
1326 return res;
1329 static int
1330 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1331 return group1->id - group2->id;
1335 * Returns string like "2 4 7 8" - group ids buddy belong to.
1337 static gchar *
1338 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1339 int i = 0;
1340 gchar *res;
1341 //creating array from GList, converting int to gchar*
1342 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1343 GSList *entry = buddy->groups;
1344 while (entry) {
1345 struct sipe_group * group = entry->data;
1346 ids_arr[i] = g_strdup_printf("%d", group->id);
1347 entry = entry->next;
1348 i++;
1350 ids_arr[i] = NULL;
1351 res = g_strjoinv(" ", ids_arr);
1352 g_strfreev(ids_arr);
1353 return res;
1357 * Sends buddy update to server
1359 static void
1360 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1362 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1363 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1365 if (buddy && purple_buddy) {
1366 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1367 gchar *body;
1368 gchar *groups = sipe_get_buddy_groups_string(buddy);
1369 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1371 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1372 alias, groups, "true", buddy->name, sip->contacts_delta++
1374 send_soap_request(sip, body);
1375 g_free(groups);
1376 g_free(body);
1380 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1382 if (msg->response == 200) {
1383 struct sipe_group *group;
1384 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1385 xmlnode *xml;
1386 xmlnode *node;
1387 char *group_id;
1388 struct sipe_buddy *buddy;
1390 xml = xmlnode_from_str(msg->body, msg->bodylen);
1391 if (!xml) {
1392 g_free(ctx);
1393 return FALSE;
1396 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1397 if (!node) {
1398 g_free(ctx);
1399 xmlnode_free(xml);
1400 return FALSE;
1403 group_id = xmlnode_get_data(node);
1404 if (!group_id) {
1405 g_free(ctx);
1406 xmlnode_free(xml);
1407 return FALSE;
1410 group = g_new0(struct sipe_group, 1);
1411 group->id = (int)g_ascii_strtod(group_id, NULL);
1412 g_free(group_id);
1413 group->name = ctx->group_name;
1415 sipe_group_add(sip, group);
1417 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1418 if (buddy) {
1419 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1422 sipe_group_set_user(sip, ctx->user_name);
1424 g_free(ctx);
1425 xmlnode_free(xml);
1426 return TRUE;
1428 return FALSE;
1431 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1433 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1434 gchar *body;
1435 ctx->group_name = g_strdup(name);
1436 ctx->user_name = g_strdup(who);
1438 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1439 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1440 g_free(body);
1444 * A timer callback
1445 * Should return FALSE if repetitive action is not needed
1447 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1449 gboolean ret;
1450 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1451 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1452 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1453 (sched_action->action)(sched_action->sip, sched_action->payload);
1454 ret = sched_action->repetitive;
1455 g_free(sched_action->payload);
1456 g_free(sched_action->name);
1457 g_free(sched_action);
1458 return ret;
1462 * Do schedule action for execution in the future.
1463 * Non repetitive execution.
1465 * @param name of action (will be copied)
1466 * @param timeout in seconds
1467 * @action callback function
1468 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1470 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1472 struct scheduled_action *sched_action;
1474 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1475 sched_action = g_new0(struct scheduled_action, 1);
1476 sched_action->repetitive = FALSE;
1477 sched_action->name = g_strdup(name);
1478 sched_action->action = action;
1479 sched_action->sip = sip;
1480 sched_action->payload = payload;
1481 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1482 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1483 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1487 * Kills action timer effectively cancelling
1488 * scheduled action
1490 * @param name of action
1492 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1494 GSList *entry;
1496 if (!sip->timeouts || !name) return;
1498 entry = sip->timeouts;
1499 while (entry) {
1500 struct scheduled_action *sched_action = entry->data;
1501 if(!strcmp(sched_action->name, name)) {
1502 GSList *to_delete = entry;
1503 entry = entry->next;
1504 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1505 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1506 purple_timeout_remove(sched_action->timeout_handler);
1507 g_free(sched_action->payload);
1508 g_free(sched_action->name);
1509 g_free(sched_action);
1510 } else {
1511 entry = entry->next;
1516 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1518 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1520 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1522 process_incoming_notify(sip, msg, FALSE, FALSE);
1524 return TRUE;
1527 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1529 gchar *tmp = *resources_uri;
1530 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1531 g_free(tmp);
1534 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1536 gchar *tmp = *resources_uri;
1537 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1538 g_free(tmp);
1542 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1543 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1544 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1545 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1546 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1549 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip){
1550 gchar *to = g_strdup_printf("sip:%s", sip->username);
1551 gchar *contact = get_contact(sip);
1552 gchar *request;
1553 gchar *content;
1554 gchar *resources_uri = g_strdup("");
1555 gchar *require = "";
1556 gchar *accept = "";
1557 gchar *autoextend = "";
1558 gchar *content_type;
1561 if (sip->msrtc_event_categories) {
1562 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1563 require = ", categoryList";
1564 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1565 content_type = "application/msrtc-adrl-categorylist+xml";
1566 content = g_strdup_printf(
1567 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1568 "<action name=\"subscribe\" id=\"63792024\">\n"
1569 "<adhocList>\n%s</adhocList>\n"
1570 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1571 "<category name=\"note\"/>\n"
1572 "<category name=\"state\"/>\n"
1573 "</categoryList>\n"
1574 "</action>\n"
1575 "</batchSub>", sip->username, resources_uri);
1576 } else {
1577 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1578 autoextend = "Supported: com.microsoft.autoextend\r\n";
1579 content_type = "application/adrl+xml";
1580 content = g_strdup_printf(
1581 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1582 "<create xmlns=\"\">\n%s</create>\n"
1583 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1585 g_free(resources_uri);
1587 request = g_strdup_printf(
1588 "Require: adhoclist%s\r\n"
1589 "Supported: eventlist\r\n"
1590 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1591 "Supported: ms-piggyback-first-notify\r\n"
1592 "%sSupported: ms-benotify\r\n"
1593 "Proxy-Require: ms-benotify\r\n"
1594 "Event: presence\r\n"
1595 "Content-Type: %s\r\n"
1596 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1597 g_free(contact);
1599 /* subscribe to buddy presence */
1600 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1601 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1603 g_free(content);
1604 g_free(to);
1605 g_free(request);
1609 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1610 * The user sends a single SUBSCRIBE request to the subscribed contact.
1611 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1615 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1617 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1618 gchar *tmp = get_contact(sip);
1619 gchar *request;
1620 gchar *content;
1621 gchar *autoextend = "";
1623 if (!sip->msrtc_event_categories)
1624 autoextend = "Supported: com.microsoft.autoextend\r\n";
1626 request = g_strdup_printf(
1627 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1628 "Supported: ms-piggyback-first-notify\r\n"
1629 "%sSupported: ms-benotify\r\n"
1630 "Proxy-Require: ms-benotify\r\n"
1631 "Event: presence\r\n"
1632 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1633 "Contact: %s\r\n", autoextend,tmp);
1635 content = g_strdup_printf(
1636 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1637 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1638 "<resource uri=\"%s\"/>\n"
1639 "</adhocList>\n"
1640 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1641 "<category name=\"note\"/>\n"
1642 "<category name=\"state\"/>\n"
1643 "</categoryList>\n"
1644 "</action>\n"
1645 "</batchSub>", sip->username, to
1648 g_free(tmp);
1650 /* subscribe to buddy presence */
1651 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1653 g_free(content);
1654 g_free(to);
1655 g_free(request);
1658 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1660 if (!purple_status_is_active(status))
1661 return;
1663 if (account->gc) {
1664 struct sipe_account_data *sip = account->gc->proto_data;
1666 if (sip) {
1667 g_free(sip->status);
1668 sip->status = g_strdup(purple_status_get_id(status));
1669 send_presence_status(sip);
1674 static void
1675 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1677 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1678 sipe_group_set_user(sip, name);
1681 static void
1682 sipe_group_buddy(PurpleConnection *gc,
1683 const char *who,
1684 const char *old_group_name,
1685 const char *new_group_name)
1687 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1688 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1689 struct sipe_group * old_group = NULL;
1690 struct sipe_group * new_group;
1692 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1693 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1695 if(!buddy) { // buddy not in roaming list
1696 return;
1699 if (old_group_name) {
1700 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1702 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1704 if (old_group) {
1705 buddy->groups = g_slist_remove(buddy->groups, old_group);
1706 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1709 if (!new_group) {
1710 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1711 } else {
1712 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1713 sipe_group_set_user(sip, who);
1717 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1719 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1720 struct sipe_buddy *b;
1722 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1724 // Prepend sip: if needed
1725 if (strncmp("sip:", buddy->name, 4)) {
1726 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1727 purple_blist_rename_buddy(buddy, buf);
1728 g_free(buf);
1731 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1732 b = g_new0(struct sipe_buddy, 1);
1733 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1734 b->name = g_strdup(buddy->name);
1735 g_hash_table_insert(sip->buddies, b->name, b);
1736 sipe_group_buddy(gc, b->name, NULL, group->name);
1737 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1738 } else {
1739 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1743 static void sipe_free_buddy(struct sipe_buddy *buddy)
1745 g_free(buddy->name);
1746 g_free(buddy->annotation);
1747 g_free(buddy->device_name);
1748 g_slist_free(buddy->groups);
1749 g_free(buddy);
1753 * Unassociates buddy from group first.
1754 * Then see if no groups left, removes buddy completely.
1755 * Otherwise updates buddy groups on server.
1757 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1759 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1760 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1761 struct sipe_group *g = NULL;
1763 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1765 if (!b) return;
1767 if (group) {
1768 g = sipe_group_find_by_name(sip, group->name);
1771 if (g) {
1772 b->groups = g_slist_remove(b->groups, g);
1773 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1776 if (g_slist_length(b->groups) < 1) {
1777 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1778 sipe_cancel_scheduled_action(sip, action_name);
1779 g_free(action_name);
1781 g_hash_table_remove(sip->buddies, buddy->name);
1783 if (b->name) {
1784 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1785 send_soap_request(sip, body);
1786 g_free(body);
1789 sipe_free_buddy(b);
1790 } else {
1791 //updates groups on server
1792 sipe_group_set_user(sip, b->name);
1797 static void
1798 sipe_rename_group(PurpleConnection *gc,
1799 const char *old_name,
1800 PurpleGroup *group,
1801 GList *moved_buddies)
1803 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1804 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1805 if (group) {
1806 sipe_group_rename(sip, s_group, group->name);
1807 } else {
1808 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1812 static void
1813 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1815 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1816 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1817 if (s_group) {
1818 gchar *body;
1819 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1820 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1821 send_soap_request(sip, body);
1822 g_free(body);
1824 sip->groups = g_slist_remove(sip->groups, s_group);
1825 g_free(s_group->name);
1826 g_free(s_group);
1827 } else {
1828 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1832 static GList *sipe_status_types(PurpleAccount *acc)
1834 PurpleStatusType *type;
1835 GList *types = NULL;
1837 // Online
1838 type = purple_status_type_new_with_attrs(
1839 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1840 // Translators: noun
1841 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1842 NULL);
1843 types = g_list_append(types, type);
1845 // Busy
1846 type = purple_status_type_new_with_attrs(
1847 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1848 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1849 NULL);
1850 types = g_list_append(types, type);
1852 // Do Not Disturb (not user settable)
1853 type = purple_status_type_new_with_attrs(
1854 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1855 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1856 NULL);
1857 types = g_list_append(types, type);
1859 // Be Right Back
1860 type = purple_status_type_new_with_attrs(
1861 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1862 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1863 NULL);
1864 types = g_list_append(types, type);
1866 // Away
1867 type = purple_status_type_new_with_attrs(
1868 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1869 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1870 NULL);
1871 types = g_list_append(types, type);
1873 //On The Phone
1874 type = purple_status_type_new_with_attrs(
1875 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1876 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1877 NULL);
1878 types = g_list_append(types, type);
1880 //Out To Lunch
1881 type = purple_status_type_new_with_attrs(
1882 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1883 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1884 NULL);
1885 types = g_list_append(types, type);
1887 //Appear Offline
1888 type = purple_status_type_new_full(
1889 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1890 types = g_list_append(types, type);
1892 // Offline
1893 type = purple_status_type_new_full(
1894 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1895 types = g_list_append(types, type);
1897 return types;
1901 * A callback for g_hash_table_foreach
1903 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1905 sipe_subscribe_presence_single(sip, buddy->name);
1909 * Removes entries from purple buddy list
1910 * that does not correspond ones in the roaming contact list.
1912 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1913 GSList *buddies = purple_find_buddies(sip->account, NULL);
1914 GSList *entry = buddies;
1915 struct sipe_buddy *buddy;
1916 PurpleBuddy *b;
1917 PurpleGroup *g;
1919 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1920 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1921 while (entry) {
1922 b = entry->data;
1923 g = purple_buddy_get_group(b);
1924 buddy = g_hash_table_lookup(sip->buddies, b->name);
1925 if(buddy) {
1926 gboolean in_sipe_groups = FALSE;
1927 GSList *entry2 = buddy->groups;
1928 while (entry2) {
1929 struct sipe_group *group = entry2->data;
1930 if (!strcmp(group->name, g->name)) {
1931 in_sipe_groups = TRUE;
1932 break;
1934 entry2 = entry2->next;
1936 if(!in_sipe_groups) {
1937 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1938 purple_blist_remove_buddy(b);
1940 } else {
1941 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1942 purple_blist_remove_buddy(b);
1944 entry = entry->next;
1948 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1950 int len = msg->bodylen;
1952 gchar *tmp = sipmsg_find_header(msg, "Event");
1953 xmlnode *item;
1954 xmlnode *isc;
1955 const gchar *contacts_delta;
1956 xmlnode *group_node;
1957 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1958 return FALSE;
1961 /* Convert the contact from XML to Purple Buddies */
1962 isc = xmlnode_from_str(msg->body, len);
1963 if (!isc) {
1964 return FALSE;
1967 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1968 if (contacts_delta) {
1969 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1972 /* Parse groups */
1973 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1974 struct sipe_group * group = g_new0(struct sipe_group, 1);
1975 const char *name = xmlnode_get_attrib(group_node, "name");
1977 if (!strncmp(name, "~", 1)) {
1978 // TODO translate
1979 name = "Other Contacts";
1981 group->name = g_strdup(name);
1982 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1984 sipe_group_add(sip, group);
1987 // Make sure we have at least one group
1988 if (g_slist_length(sip->groups) == 0) {
1989 struct sipe_group * group = g_new0(struct sipe_group, 1);
1990 PurpleGroup *purple_group;
1991 // TODO translate
1992 group->name = g_strdup("Other Contacts");
1993 group->id = 1;
1994 purple_group = purple_group_new(group->name);
1995 purple_blist_add_group(purple_group, NULL);
1996 sip->groups = g_slist_append(sip->groups, group);
1999 /* Parse contacts */
2000 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2001 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
2002 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
2003 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
2004 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
2005 gchar **item_groups;
2006 struct sipe_group *group = NULL;
2007 struct sipe_buddy *buddy = NULL;
2008 int i = 0;
2010 // assign to group Other Contacts if nothing else received
2011 if(!groups || !strcmp("", groups) ) {
2012 group = sipe_group_find_by_name(sip, "Other Contacts");
2013 groups = group ? g_strdup_printf("%d", group->id) : "1";
2016 item_groups = g_strsplit(groups, " ", 0);
2018 while (item_groups[i]) {
2019 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2021 // If couldn't find the right group for this contact, just put them in the first group we have
2022 if (group == NULL && g_slist_length(sip->groups) > 0) {
2023 group = sip->groups->data;
2026 if (group != NULL) {
2027 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2028 if (!b){
2029 b = purple_buddy_new(sip->account, buddy_name, uri);
2030 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2033 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2034 if (name != NULL && strlen(name) != 0) {
2035 purple_blist_alias_buddy(b, name);
2039 if (!buddy) {
2040 buddy = g_new0(struct sipe_buddy, 1);
2041 buddy->name = g_strdup(b->name);
2042 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2045 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2047 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2048 } else {
2049 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2050 name);
2053 i++;
2054 } // while, contact groups
2055 g_strfreev(item_groups);
2056 g_free(groups);
2057 g_free(name);
2058 g_free(buddy_name);
2059 g_free(uri);
2061 } // for, contacts
2063 xmlnode_free(isc);
2065 sipe_cleanup_local_blist(sip);
2067 //subscribe to buddies
2068 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2069 //if(sip->msrtc_event_categories){
2070 sipe_subscribe_presence_batched(sip);
2071 //}else{
2072 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2074 sip->subscribed_buddies = TRUE;
2077 return 0;
2081 * Subscribe roaming contacts
2083 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2085 gchar *to = g_strdup_printf("sip:%s", sip->username);
2086 gchar *tmp = get_contact(sip);
2087 gchar *hdr = g_strdup_printf(
2088 "Event: vnd-microsoft-roaming-contacts\r\n"
2089 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2090 "Supported: com.microsoft.autoextend\r\n"
2091 "Supported: ms-benotify\r\n"
2092 "Proxy-Require: ms-benotify\r\n"
2093 "Supported: ms-piggyback-first-notify\r\n"
2094 "Contact: %s\r\n", tmp);
2095 g_free(tmp);
2097 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2098 g_free(to);
2099 g_free(hdr);
2102 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2104 gchar *to = g_strdup_printf("sip:%s", sip->username);
2105 gchar *tmp = get_contact(sip);
2106 gchar *hdr = g_strdup_printf(
2107 "Event: presence.wpending\r\n"
2108 "Accept: text/xml+msrtc.wpending\r\n"
2109 "Supported: com.microsoft.autoextend\r\n"
2110 "Supported: ms-benotify\r\n"
2111 "Proxy-Require: ms-benotify\r\n"
2112 "Supported: ms-piggyback-first-notify\r\n"
2113 "Contact: %s\r\n", tmp);
2114 g_free(tmp);
2116 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2117 g_free(to);
2118 g_free(hdr);
2122 * Fires on deregistration event initiated by server.
2123 * [MS-SIPREGE] SIP extension.
2126 // 2007 Example
2128 // Content-Type: text/registration-event
2129 // subscription-state: terminated;expires=0
2130 // ms-diagnostics-public: 4141;reason="User disabled"
2132 // deregistered;event=rejected
2134 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2136 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2137 gchar *event = NULL;
2138 gchar *reason = NULL;
2139 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2141 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2142 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2144 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2145 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2146 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2147 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2148 } else {
2149 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2150 return;
2153 if (warning != NULL) {
2154 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2155 } else { // for LCS2005
2156 int error_id = 0;
2157 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2158 error_id = 4140; // [MS-SIPREGE]
2159 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2160 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2161 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2162 error_id = 4141;
2163 reason = g_strdup(_("User disabled")); // [MS-OCER]
2164 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2165 error_id = 4142;
2166 reason = g_strdup(_("User moved")); // [MS-OCER]
2169 g_free(event);
2170 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2171 g_free(reason);
2173 sip->gc->wants_to_die = TRUE;
2174 purple_connection_error(sip->gc, warning);
2175 g_free(warning);
2179 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2181 const gchar *contacts_delta;
2182 xmlnode *xml;
2184 xml = xmlnode_from_str(msg->body, msg->bodylen);
2185 if (!xml)
2187 return;
2190 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2191 if (contacts_delta)
2193 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2196 xmlnode_free(xml);
2202 * When we receive some self (BE) NOTIFY with a new subscriber
2203 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2207 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2209 gchar *contact;
2210 gchar *to;
2211 xmlnode *xml;
2212 xmlnode *node;
2213 char *display_name = NULL;
2214 PurpleBuddy *pbuddy;
2215 const char *alias;
2216 char *uri_alias;
2217 char *uri_user;
2219 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2221 xml = xmlnode_from_str(msg->body, msg->bodylen);
2222 if (!xml) return;
2224 contact = get_contact(sip);
2225 to = g_strdup_printf("sip:%s", sip->username);
2227 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2228 const char *user;
2229 const char *acknowledged;
2230 gchar *hdr;
2231 gchar *body;
2233 user = xmlnode_get_attrib(node, "user");
2234 if (!user) continue;
2235 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2236 uri_user = g_strdup_printf("sip:%s", user);
2237 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2238 if(pbuddy){
2239 alias = purple_buddy_get_local_alias(pbuddy);
2240 uri_alias = g_strdup_printf("sip:%s", alias);
2241 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2242 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2243 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2244 purple_blist_alias_buddy(pbuddy, display_name);
2246 g_free(uri_alias);
2248 g_free(uri_user);
2250 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2251 if(!g_ascii_strcasecmp(acknowledged,"false")){
2252 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2253 hdr = g_strdup_printf(
2254 "Contact: %s\r\n"
2255 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2257 body = g_strdup_printf(
2258 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2259 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2260 "</setSubscribers>", user);
2262 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2263 g_free(body);
2264 g_free(hdr);
2268 g_free(to);
2269 g_free(contact);
2270 xmlnode_free(xml);
2273 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2275 gchar *to = g_strdup_printf("sip:%s", sip->username);
2276 gchar *tmp = get_contact(sip);
2277 gchar *hdr = g_strdup_printf(
2278 "Event: vnd-microsoft-roaming-ACL\r\n"
2279 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2280 "Supported: com.microsoft.autoextend\r\n"
2281 "Supported: ms-benotify\r\n"
2282 "Proxy-Require: ms-benotify\r\n"
2283 "Supported: ms-piggyback-first-notify\r\n"
2284 "Contact: %s\r\n", tmp);
2285 g_free(tmp);
2287 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2288 g_free(to);
2289 g_free(hdr);
2293 * To request for presence information about the user, access level settings that have already been configured by the user
2294 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2295 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2298 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2300 gchar *to = g_strdup_printf("sip:%s", sip->username);
2301 gchar *tmp = get_contact(sip);
2302 gchar *hdr = g_strdup_printf(
2303 "Event: vnd-microsoft-roaming-self\r\n"
2304 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2305 "Supported: ms-benotify\r\n"
2306 "Proxy-Require: ms-benotify\r\n"
2307 "Supported: ms-piggyback-first-notify\r\n"
2308 "Contact: %s\r\n"
2309 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2311 gchar *body=g_strdup(
2312 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2313 "<roaming type=\"categories\"/>"
2314 "<roaming type=\"containers\"/>"
2315 "<roaming type=\"subscribers\"/></roamingList>");
2317 g_free(tmp);
2318 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2319 g_free(body);
2320 g_free(to);
2321 g_free(hdr);
2325 * For 2005 version
2327 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2329 gchar *to = g_strdup_printf("sip:%s", sip->username);
2330 gchar *tmp = get_contact(sip);
2331 gchar *hdr = g_strdup_printf(
2332 "Event: vnd-microsoft-provisioning\r\n"
2333 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2334 "Supported: com.microsoft.autoextend\r\n"
2335 "Supported: ms-benotify\r\n"
2336 "Proxy-Require: ms-benotify\r\n"
2337 "Supported: ms-piggyback-first-notify\r\n"
2338 "Expires: 0\r\n"
2339 "Contact: %s\r\n", tmp);
2341 g_free(tmp);
2342 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2343 g_free(to);
2344 g_free(hdr);
2347 /** Subscription for provisioning information to help with initial
2348 * configuration. This subscription is a one-time query (denoted by the Expires header,
2349 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2350 * configuration, meeting policies, and policy settings that Communicator must enforce.
2351 * TODO: for what we need this information.
2354 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2356 gchar *to = g_strdup_printf("sip:%s", sip->username);
2357 gchar *tmp = get_contact(sip);
2358 gchar *hdr = g_strdup_printf(
2359 "Event: vnd-microsoft-provisioning-v2\r\n"
2360 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2361 "Supported: com.microsoft.autoextend\r\n"
2362 "Supported: ms-benotify\r\n"
2363 "Proxy-Require: ms-benotify\r\n"
2364 "Supported: ms-piggyback-first-notify\r\n"
2365 "Expires: 0\r\n"
2366 "Contact: %s\r\n"
2367 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2368 gchar *body = g_strdup(
2369 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2370 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2371 "<provisioningGroup name=\"ucPolicy\"/>"
2372 "</provisioningGroupList>");
2374 g_free(tmp);
2375 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2376 g_free(body);
2377 g_free(to);
2378 g_free(hdr);
2381 /* IM Session (INVITE and MESSAGE methods) */
2383 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2385 struct sip_im_session *session;
2386 GSList *entry;
2387 if (sip == NULL || who == NULL) {
2388 return NULL;
2391 entry = sip->im_sessions;
2392 while (entry) {
2393 session = entry->data;
2394 if ((who != NULL && !strcmp(who, session->with))) {
2395 return session;
2397 entry = entry->next;
2399 return NULL;
2402 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2404 struct sip_im_session *session = find_im_session(sip, who);
2405 if (!session) {
2406 session = g_new0(struct sip_im_session, 1);
2407 session->with = g_strdup(who);
2408 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2409 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2411 return session;
2414 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2416 struct sip_dialog *dialog = session->dialog;
2417 GSList *entry;
2419 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2421 if (dialog) {
2422 entry = dialog->routes;
2423 while (entry) {
2424 g_free(entry->data);
2425 entry = g_slist_remove(entry, entry->data);
2427 entry = dialog->supported;
2428 while (entry) {
2429 g_free(entry->data);
2430 entry = g_slist_remove(entry, entry->data);
2432 g_free(dialog->callid);
2433 g_free(dialog->ourtag);
2434 g_free(dialog->theirtag);
2435 g_free(dialog->theirepid);
2436 g_free(dialog->request);
2438 g_free(session->dialog);
2440 entry = session->outgoing_message_queue;
2441 while (entry) {
2442 g_free(entry->data);
2443 entry = g_slist_remove(entry, entry->data);
2446 g_hash_table_destroy(session->unconfirmed_messages);
2448 g_free(session->with);
2449 g_free(session);
2452 static gboolean
2453 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2455 gboolean ret = TRUE;
2457 if (msg->response != 200) {
2458 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2459 return FALSE;
2462 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2464 return ret;
2468 * Asks UA/proxy about its capabilities.
2470 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2472 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2473 gchar *contact = get_contact(sip);
2474 gchar *request;
2475 request = g_strdup_printf(
2476 "Accept: application/sdp\r\n"
2477 "Contact: %s\r\n", contact);
2479 g_free(contact);
2481 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2483 g_free(to);
2484 g_free(request);
2487 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2489 char *msg, *msg_tmp;
2490 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2491 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2492 g_free(msg_tmp);
2493 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2494 "possibly because one or more persons are offline:\n%s") ,
2495 msg ? msg : "");
2496 purple_conv_present_error(with, sip->account, msg_tmp);
2497 g_free(msg);
2498 g_free(msg_tmp);
2501 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2503 static gboolean
2504 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2506 gboolean ret = TRUE;
2507 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2508 struct sip_im_session * session = find_im_session(sip, with);
2509 struct sip_dialog *dialog;
2510 gchar *cseq;
2511 char *key;
2512 gchar *message;
2514 if (!session) {
2515 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2516 g_free(with);
2517 return FALSE;
2520 dialog = session->dialog;
2521 if (!dialog) {
2522 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2523 g_free(with);
2524 return FALSE;
2527 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2528 key = g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq));
2529 g_free(cseq);
2530 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2532 if (msg->response != 200) {
2533 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2535 sipe_present_message_undelivered_err(with, sip, message);
2536 im_session_destroy(sip, session);
2537 ret = FALSE;
2538 } else {
2539 g_hash_table_remove(session->unconfirmed_messages, key);
2540 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2541 key, g_hash_table_size(session->unconfirmed_messages));
2544 g_free(key);
2545 g_free(with);
2547 sipe_im_process_queue(sip, session);
2548 return ret;
2551 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2553 gchar *hdr;
2554 gchar *fullto;
2555 gchar *tmp;
2556 char *msgformat;
2557 char *msgtext;
2558 gchar *msgr_value;
2559 gchar *msgr;
2561 if (strncmp("sip:", session->with, 4)) {
2562 fullto = g_strdup_printf("sip:%s", session->with);
2563 } else {
2564 fullto = g_strdup(session->with);
2567 sipe_parse_html(msg, &msgformat, &msgtext);
2568 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2570 msgr_value = sipmsg_get_msgr_string(msgformat);
2571 g_free(msgformat);
2572 if (msgr_value) {
2573 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2574 g_free(msgr_value);
2575 } else {
2576 msgr = g_strdup("");
2579 tmp = get_contact(sip);
2580 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2581 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2582 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2583 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2584 tmp, msgr);
2585 g_free(tmp);
2586 g_free(msgr);
2588 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2589 g_free(msgtext);
2590 g_free(hdr);
2591 g_free(fullto);
2595 static void
2596 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2598 GSList *entry = session->outgoing_message_queue;
2600 if (session->outgoing_invite) return; //do not send messages until INVITE responded.
2602 while (entry) {
2603 char *key = g_strdup_printf("<%s><%d><MESSAGE>", session->dialog->callid, (session->dialog->cseq) + 1);
2604 char *queued_msg = entry->data;
2605 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2606 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2607 key, g_hash_table_size(session->unconfirmed_messages));
2608 g_free(key);
2609 sipe_send_message(sip, session, queued_msg);
2610 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2611 g_free(queued_msg);
2615 static void
2616 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2618 GSList *hdr = msg->headers;
2619 struct siphdrelement *elem;
2620 gchar *contact;
2622 while(hdr)
2624 elem = hdr->data;
2625 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2627 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2628 dialog->routes = g_slist_append(dialog->routes, route);
2630 hdr = g_slist_next(hdr);
2633 if (outgoing)
2635 dialog->routes = g_slist_reverse(dialog->routes);
2638 if (dialog->routes)
2640 dialog->request = dialog->routes->data;
2641 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2644 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2645 dialog->routes = g_slist_append(dialog->routes, contact);
2648 static void
2649 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2651 GSList *hdr = msg->headers;
2652 struct siphdrelement *elem;
2653 while(hdr)
2655 elem = hdr->data;
2656 if(!g_ascii_strcasecmp(elem->name, "Supported")
2657 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2659 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2662 hdr = g_slist_next(hdr);
2666 static void
2667 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2669 gchar *us = outgoing ? "From" : "To";
2670 gchar *them = outgoing ? "To" : "From";
2672 g_free(dialog->callid);
2673 g_free(dialog->ourtag);
2674 g_free(dialog->theirtag);
2676 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2677 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2678 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2679 if (!dialog->theirepid) {
2680 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2681 if (!dialog->theirepid) {
2682 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2686 sipe_get_route_header(msg, dialog, outgoing);
2687 sipe_get_supported_header(msg, dialog, outgoing);
2691 static gboolean
2692 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2694 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2695 struct sip_im_session * session = find_im_session(sip, with);
2696 struct sip_dialog *dialog;
2697 char *cseq;
2698 char *key;
2699 gchar *message;
2701 if (!session) {
2702 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2703 g_free(with);
2704 return FALSE;
2707 dialog = session->dialog;
2708 if (!dialog) {
2709 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2710 g_free(with);
2711 return FALSE;
2714 sipe_parse_dialog(msg, dialog, TRUE);
2716 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2717 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2718 g_free(cseq);
2719 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2721 if (msg->response != 200) {
2722 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2724 sipe_present_message_undelivered_err(with, sip, message);
2725 im_session_destroy(sip, session);
2726 g_free(with);
2727 return FALSE;
2730 dialog->cseq = 0;
2731 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2732 session->outgoing_invite = NULL;
2733 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2734 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2735 if (session->outgoing_message_queue) {
2736 char *queued_msg = session->outgoing_message_queue->data;
2737 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2738 g_free(queued_msg);
2742 sipe_im_process_queue(sip, session);
2744 g_hash_table_remove(session->unconfirmed_messages, key);
2745 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2746 key, g_hash_table_size(session->unconfirmed_messages));
2748 g_free(key);
2749 g_free(with);
2750 return TRUE;
2754 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session *session, const gchar *msg_body)
2756 gchar *hdr;
2757 gchar *to;
2758 gchar *contact;
2759 gchar *body;
2760 char *msgformat;
2761 char *msgtext;
2762 char *base64_msg;
2763 char *ms_text_format;
2764 gchar *msgr_value;
2765 gchar *msgr;
2766 char *key;
2768 if (session->dialog) {
2769 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2770 return;
2773 session->dialog = g_new0(struct sip_dialog, 1);
2774 session->dialog->callid = gencallid();
2776 if (strstr(session->with, "sip:")) {
2777 to = g_strdup(session->with);
2778 } else {
2779 to = g_strdup_printf("sip:%s", session->with);
2782 sipe_parse_html(msg_body, &msgformat, &msgtext);
2783 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2785 msgr_value = sipmsg_get_msgr_string(msgformat);
2786 g_free(msgformat);
2787 msgr = "";
2788 if (msgr_value) {
2789 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2790 g_free(msgr_value);
2793 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2794 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2795 g_free(msgtext);
2796 g_free(msgr);
2797 g_free(base64_msg);
2799 key = g_strdup_printf("<%s><%d><INVITE>", session->dialog->callid, (session->dialog->cseq) + 1);
2800 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2801 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2802 key, g_hash_table_size(session->unconfirmed_messages));
2803 g_free(key);
2805 contact = get_contact(sip);
2806 hdr = g_strdup_printf(
2807 "Contact: %s\r\n%s"
2808 "Content-Type: application/sdp\r\n",
2809 contact, ms_text_format);
2810 g_free(ms_text_format);
2812 body = g_strdup_printf(
2813 "v=0\r\n"
2814 "o=- 0 0 IN IP4 %s\r\n"
2815 "s=session\r\n"
2816 "c=IN IP4 %s\r\n"
2817 "t=0 0\r\n"
2818 "m=message %d sip null\r\n"
2819 "a=accept-types:text/plain text/html image/gif "
2820 "multipart/alternative application/im-iscomposing+xml\r\n",
2821 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2823 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2824 to, to, hdr, body, session->dialog, process_invite_response);
2826 g_free(to);
2827 g_free(body);
2828 g_free(hdr);
2829 g_free(contact);
2832 static void
2833 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2835 if (session) {
2836 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2837 im_session_destroy(sip, session);
2841 static void
2842 sipe_convo_closed(PurpleConnection * gc, const char *who)
2844 struct sipe_account_data *sip = gc->proto_data;
2846 purple_debug_info("sipe", "conversation with %s closed\n", who);
2847 im_session_close(sip, find_im_session(sip, who));
2850 static void
2851 im_session_close_all (struct sipe_account_data *sip)
2853 GSList *entry = sip->im_sessions;
2854 while (entry) {
2855 im_session_close (sip, entry->data);
2856 entry = sip->im_sessions;
2860 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2862 struct sipe_account_data *sip;
2863 gchar *to;
2864 struct sip_im_session *session;
2866 sip = gc->proto_data;
2867 to = g_strdup(who);
2869 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2871 session = find_or_create_im_session(sip, who);
2873 // Queue the message
2874 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
2876 if (session->dialog && session->dialog->callid) {
2877 sipe_im_process_queue(sip, session);
2878 } else if (!session->outgoing_invite) {
2879 // Need to send the INVITE to get the outgoing dialog setup
2880 sipe_invite(sip, session, what);
2883 g_free(to);
2884 return 1;
2887 /* End IM Session (INVITE and MESSAGE methods) */
2889 static unsigned int
2890 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2892 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2893 struct sip_im_session *session;
2895 if (state == PURPLE_NOT_TYPING)
2896 return 0;
2898 session = find_im_session(sip, who);
2900 if (session && session->dialog) {
2901 send_sip_request(gc, "INFO", who, who,
2902 "Content-Type: application/xml\r\n",
2903 SIPE_SEND_TYPING, session->dialog, NULL);
2905 return SIPE_TYPING_SEND_TIMEOUT;
2908 static gboolean resend_timeout(struct sipe_account_data *sip)
2910 GSList *tmp = sip->transactions;
2911 time_t currtime = time(NULL);
2912 while (tmp) {
2913 struct transaction *trans = tmp->data;
2914 tmp = tmp->next;
2915 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2916 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2917 /* TODO 408 */
2918 } else {
2919 if ((currtime - trans->time > 2) && trans->retries == 0) {
2920 trans->retries++;
2921 sendout_sipmsg(sip, trans->msg);
2925 return TRUE;
2928 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2930 /* register again when security token expires */
2931 /* we have to start a new authentication as the security token
2932 * is almost expired by sending a not signed REGISTER message */
2933 purple_debug_info("sipe", "do a full reauthentication\n");
2934 sipe_auth_free(&sip->registrar);
2935 sip->registerstatus = 0;
2936 do_register(sip);
2937 sip->reauthenticate_set = FALSE;
2940 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2942 gchar *from;
2943 gchar *contenttype;
2944 gboolean found = FALSE;
2946 from = parse_from(sipmsg_find_header(msg, "From"));
2948 if (!from) return;
2950 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2952 contenttype = sipmsg_find_header(msg, "Content-Type");
2953 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2955 gchar *html = get_html_message(contenttype, msg->body);
2956 serv_got_im(sip->gc, from, html, 0, time(NULL));
2957 g_free(html);
2958 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2959 found = TRUE;
2961 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2962 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2963 xmlnode *state;
2964 gchar *statedata;
2966 if (!isc) {
2967 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2968 return;
2971 state = xmlnode_get_child(isc, "state");
2973 if (!state) {
2974 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2975 xmlnode_free(isc);
2976 return;
2979 statedata = xmlnode_get_data(state);
2980 if (statedata) {
2981 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2982 else serv_got_typing_stopped(sip->gc, from);
2984 g_free(statedata);
2986 xmlnode_free(isc);
2987 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2988 found = TRUE;
2990 if (!found) {
2991 purple_debug_info("sipe", "got unknown mime-type");
2992 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2994 g_free(from);
2997 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2999 gchar *ms_text_format;
3000 gchar *from;
3001 gchar *body;
3002 struct sip_im_session *session;
3004 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3006 // Only accept text invitations
3007 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3008 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3009 return;
3012 from = parse_from(sipmsg_find_header(msg, "From"));
3013 session = find_or_create_im_session (sip, from);
3014 if (session) {
3015 if (session->dialog) {
3016 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3017 } else {
3018 session->dialog = g_new0(struct sip_dialog, 1);
3020 sipe_parse_dialog(msg, session->dialog, FALSE);
3022 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3023 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3024 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3025 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3027 } else {
3028 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3031 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3032 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3033 if (ms_text_format) {
3034 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3036 gchar *html = get_html_message(ms_text_format, NULL);
3037 if (html) {
3038 serv_got_im(sip->gc, from, html, 0, time(NULL));
3039 g_free(html);
3040 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3044 g_free(from);
3046 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3047 sipmsg_remove_header(msg, "Ms-Text-Format");
3048 sipmsg_remove_header(msg, "EndPoints");
3049 sipmsg_remove_header(msg, "User-Agent");
3050 sipmsg_remove_header(msg, "Roster-Manager");
3052 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3053 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3055 body = g_strdup_printf(
3056 "v=0\r\n"
3057 "o=- 0 0 IN IP4 %s\r\n"
3058 "s=session\r\n"
3059 "c=IN IP4 %s\r\n"
3060 "t=0 0\r\n"
3061 "m=message %d sip sip:%s\r\n"
3062 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3063 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3064 sip->realport, sip->username);
3065 send_sip_response(sip->gc, msg, 200, "OK", body);
3066 g_free(body);
3069 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3071 gchar *body;
3073 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3074 sipmsg_remove_header(msg, "EndPoints");
3075 sipmsg_remove_header(msg, "User-Agent");
3077 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3078 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3080 body = g_strdup_printf(
3081 "v=0\r\n"
3082 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3083 "s=session\r\n"
3084 "c=IN IP4 0.0.0.0\r\n"
3085 "t=0 0\r\n"
3086 "m=message %d sip sip:%s\r\n"
3087 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3088 sip->realport, sip->username);
3089 send_sip_response(sip->gc, msg, 200, "OK", body);
3090 g_free(body);
3093 static void sipe_connection_cleanup(struct sipe_account_data *);
3094 static void create_connection(struct sipe_account_data *, gchar *, int);
3096 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3098 gchar *tmp;
3099 gchar *timeout;
3100 const gchar *expires_header;
3101 int expires, i;
3102 GSList *hdr = msg->headers;
3103 GSList *entry;
3104 struct siphdrelement *elem;
3106 expires_header = sipmsg_find_header(msg, "Expires");
3107 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3108 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3110 switch (msg->response) {
3111 case 200:
3112 if (expires == 0) {
3113 sip->registerstatus = 0;
3114 } else {
3115 gchar *contact_hdr = NULL;
3116 gchar *gruu = NULL;
3117 gchar *epid;
3118 gchar *uuid;
3120 if (!sip->reregister_set) {
3121 gchar *action_name = g_strdup_printf("<%s>", "registration");
3122 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
3123 g_free(action_name);
3124 sip->reregister_set = TRUE;
3127 sip->registerstatus = 3;
3129 if (!sip->reauthenticate_set) {
3130 /* we have to reauthenticate as our security token expires
3131 after eight hours (be five minutes early) */
3132 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3133 guint reauth_timeout = (8 * 3600) - 360;
3134 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3135 g_free(action_name);
3136 sip->reauthenticate_set = TRUE;
3139 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3141 epid = get_epid(sip);
3142 uuid = generateUUIDfromEPID(epid);
3143 g_free(epid);
3145 // There can be multiple Contact headers (one per location where the user is logged in) so
3146 // make sure to only get the one for this uuid
3147 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3148 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3149 if (valid_contact) {
3150 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3151 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3152 g_free(valid_contact);
3153 break;
3154 } else {
3155 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3158 g_free(uuid);
3160 g_free(sip->contact);
3161 if(gruu) {
3162 sip->contact = g_strdup_printf("<%s>", gruu);
3163 g_free(gruu);
3164 } else {
3165 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3166 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);
3168 sip->msrtc_event_categories = FALSE;
3170 while(hdr)
3172 elem = hdr->data;
3173 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3174 if (strstr(elem->value, "msrtc-event-categories")) {
3175 sip->msrtc_event_categories = TRUE;
3177 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s, %d\n", elem->value, sip->msrtc_event_categories);
3179 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3180 gchar **caps = g_strsplit(elem->value,",",0);
3181 i = 0;
3182 while (caps[i]) {
3183 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3184 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3185 i++;
3187 g_strfreev(caps);
3189 hdr = g_slist_next(hdr);
3192 if (!sip->subscribed) { //do it just once, not every re-register
3193 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3194 sipe_options_request(sip, sip->sipdomain);
3196 entry = sip->allow_events;
3197 while (entry) {
3198 tmp = entry->data;
3199 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3200 sipe_subscribe_roaming_contacts(sip, msg);
3202 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3203 sipe_subscribe_roaming_acl(sip, msg);
3205 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3206 sipe_subscribe_roaming_self(sip, msg);
3208 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3209 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3210 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3211 sipe_subscribe_roaming_provisioning(sip, msg);
3213 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3214 sipe_subscribe_presence_wpending(sip, msg);
3216 entry = entry->next;
3218 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3219 sip->subscribed = TRUE;
3222 /*if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
3223 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
3224 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
3225 } else {*/
3226 tmp = sipmsg_find_header(msg, "ms-keep-alive");
3227 timeout = sipmsg_find_part_of_header(tmp, "timeout=", ";", NULL);
3228 if (timeout) {
3229 sipe_keep_alive_timeout(sip, timeout);
3231 else{
3232 sipe_keep_alive_timeout(sip, g_strdup("300"));
3234 /*}*/
3236 // Should we remove the transaction here?
3237 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3238 transactions_remove(sip, tc);
3240 break;
3241 case 301:
3243 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3245 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3246 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3247 gchar **tmp;
3248 gchar *hostname;
3249 int port = 0;
3250 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3251 int i = 1;
3253 tmp = g_strsplit(parts[0], ":", 0);
3254 hostname = g_strdup(tmp[0]);
3255 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3256 g_strfreev(tmp);
3258 while (parts[i]) {
3259 tmp = g_strsplit(parts[i], "=", 0);
3260 if (tmp[1]) {
3261 if (g_strcasecmp("transport", tmp[0]) == 0) {
3262 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3263 transport = SIPE_TRANSPORT_TCP;
3264 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3265 transport = SIPE_TRANSPORT_UDP;
3269 g_strfreev(tmp);
3270 i++;
3272 g_strfreev(parts);
3274 /* Close old connection */
3275 sipe_connection_cleanup(sip);
3277 /* Create new connection */
3278 sip->transport = transport;
3279 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3280 hostname, port, TRANSPORT_DESCRIPTOR);
3281 create_connection(sip, hostname, port);
3283 g_free(redirect);
3285 break;
3286 case 401:
3287 if (sip->registerstatus != 2) {
3288 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3289 if (sip->registrar.retries > 3) {
3290 sip->gc->wants_to_die = TRUE;
3291 purple_connection_error(sip->gc, _("Wrong Password"));
3292 return TRUE;
3294 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3295 tmp = sipmsg_find_auth_header(msg, "NTLM");
3296 } else {
3297 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3299 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3300 fill_auth(sip, tmp, &sip->registrar);
3301 sip->registerstatus = 2;
3302 if (sip->account->disconnecting) {
3303 do_register_exp(sip, 0);
3304 } else {
3305 do_register(sip);
3308 break;
3309 case 403:
3311 gchar *warning = sipmsg_find_header(msg, "Warning");
3312 if (warning != NULL) {
3313 /* Example header:
3314 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3316 gchar **tmp = g_strsplit(warning, "\"", 0);
3317 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3318 g_strfreev(tmp);
3319 } else {
3320 warning = g_strdup(_("You have been rejected by the server"));
3323 sip->gc->wants_to_die = TRUE;
3324 purple_connection_error(sip->gc, warning);
3325 g_free(warning);
3326 return TRUE;
3328 break;
3329 case 404:
3331 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3332 if (warning != NULL) {
3333 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3334 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3335 g_free(reason);
3336 } else {
3337 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3340 sip->gc->wants_to_die = TRUE;
3341 purple_connection_error(sip->gc, warning);
3342 g_free(warning);
3343 return TRUE;
3345 break;
3346 case 503:
3348 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3349 if (warning != NULL) {
3350 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3351 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3352 g_free(reason);
3353 } else {
3354 warning = g_strdup(_("Service unavailable: no reason given"));
3357 sip->gc->wants_to_die = TRUE;
3358 purple_connection_error(sip->gc, warning);
3359 g_free(warning);
3360 return TRUE;
3362 break;
3364 return TRUE;
3367 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3369 const char *uri;
3370 xmlnode *xn_categories;
3371 xmlnode *xn_category;
3372 xmlnode *xn_node;
3373 const char *activity = NULL;
3375 xn_categories = xmlnode_from_str(data, len);
3376 uri = xmlnode_get_attrib(xn_categories, "uri");
3378 for (xn_category = xmlnode_get_child(xn_categories, "category");
3379 xn_category ;
3380 xn_category = xmlnode_get_next_twin(xn_category) )
3382 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3384 if (!strcmp(attrVar, "note"))
3386 char *note;
3387 struct sipe_buddy *sbuddy;
3388 xn_node = xmlnode_get_child(xn_category, "note");
3389 if (!xn_node) continue;
3390 xn_node = xmlnode_get_child(xn_node, "body");
3391 if (!xn_node) continue;
3393 note = xmlnode_get_data(xn_node);
3395 if(uri){
3396 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3398 if (sbuddy && note)
3400 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note);
3401 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3402 sbuddy->annotation = g_strdup(note);
3404 if(note)
3405 g_free(note);
3407 else if(!strcmp(attrVar, "state"))
3409 char *data;
3410 int avail;
3411 xn_node = xmlnode_get_child(xn_category, "state");
3412 if (!xn_node) continue;
3413 xn_node = xmlnode_get_child(xn_node, "availability");
3414 if (!xn_node) continue;
3416 data = xmlnode_get_data(xn_node);
3417 avail = atoi(data);
3418 g_free(data);
3420 if (avail < 3000)
3421 activity = SIPE_STATUS_ID_UNKNOWN;
3422 else if (avail < 4500)
3423 activity = SIPE_STATUS_ID_AVAILABLE;
3424 else if (avail < 6000)
3425 activity = SIPE_STATUS_ID_BRB;
3426 else if (avail < 7500)
3427 activity = SIPE_STATUS_ID_ONPHONE;
3428 else if (avail < 9000)
3429 activity = SIPE_STATUS_ID_BUSY;
3430 else if (avail < 12000)
3431 activity = SIPE_STATUS_ID_DND;
3432 else if (avail < 18000)
3433 activity = SIPE_STATUS_ID_AWAY;
3434 else
3435 activity = SIPE_STATUS_ID_OFFLINE;
3438 if(activity) {
3439 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
3440 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3443 xmlnode_free(xn_categories);
3446 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3448 const char *uri,*state;
3449 xmlnode *xn_list;
3450 xmlnode *xn_resource;
3451 xmlnode *xn_instance;
3453 xn_list = xmlnode_from_str(data, len);
3455 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3456 xn_resource;
3457 xn_resource = xmlnode_get_next_twin(xn_resource) )
3459 uri = xmlnode_get_attrib(xn_resource, "uri");
3460 xn_instance = xmlnode_get_child(xn_resource, "instance");
3461 if (!xn_instance) return;
3463 state = xmlnode_get_attrib(xn_instance, "state");
3464 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3465 if(strstr(state,"resubscribe")){
3466 sipe_subscribe_presence_single(sip, uri);
3471 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3473 const gchar *uri;
3474 gchar *getbasic;
3475 gchar *activity = NULL;
3476 xmlnode *pidf;
3477 xmlnode *basicstatus = NULL, *tuple, *status;
3478 gboolean isonline = FALSE;
3479 xmlnode *display_name_node;
3481 pidf = xmlnode_from_str(data, len);
3482 if (!pidf) {
3483 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3484 return;
3487 uri = xmlnode_get_attrib(pidf, "entity");
3489 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3491 if ((status = xmlnode_get_child(tuple, "status"))) {
3492 basicstatus = xmlnode_get_child(status, "basic");
3496 if (!basicstatus) {
3497 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3498 xmlnode_free(pidf);
3499 return;
3502 getbasic = xmlnode_get_data(basicstatus);
3503 if (!getbasic) {
3504 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3505 xmlnode_free(pidf);
3506 return;
3509 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3510 if (strstr(getbasic, "open")) {
3511 isonline = TRUE;
3513 g_free(getbasic);
3515 display_name_node = xmlnode_get_child(pidf, "display-name");
3516 // updating display name if alias was just URI
3517 if (display_name_node) {
3518 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3519 GSList *entry = buddies;
3520 PurpleBuddy *p_buddy;
3521 char * display_name = xmlnode_get_data(display_name_node);
3523 while (entry) {
3524 const char *server_alias;
3525 char *alias;
3527 p_buddy = entry->data;
3529 alias = (char *)purple_buddy_get_alias(p_buddy);
3530 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3531 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3532 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3533 purple_blist_alias_buddy(p_buddy, display_name);
3535 g_free(alias);
3537 server_alias = purple_buddy_get_server_alias(p_buddy);
3538 if (display_name &&
3539 ( (server_alias && strcmp(display_name, server_alias))
3540 || !server_alias || strlen(server_alias) == 0 )
3542 purple_blist_server_alias_buddy(p_buddy, display_name);
3545 entry = entry->next;
3547 g_free(display_name);
3550 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3551 if ((status = xmlnode_get_child(tuple, "status"))) {
3552 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3553 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3554 activity = xmlnode_get_data(basicstatus);
3555 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3561 if (isonline) {
3562 const gchar * status_id = NULL;
3563 if (activity) {
3564 if (strstr(activity, "busy")) {
3565 status_id = SIPE_STATUS_ID_BUSY;
3566 } else if (strstr(activity, "away")) {
3567 status_id = SIPE_STATUS_ID_AWAY;
3571 if (!status_id) {
3572 status_id = SIPE_STATUS_ID_AVAILABLE;
3575 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3576 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3577 } else {
3578 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
3581 g_free(activity);
3582 xmlnode_free(pidf);
3585 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3587 const char *availability;
3588 const char *activity;
3589 const char *display_name = NULL;
3590 const char *activity_name = NULL;
3591 const char *name;
3592 char *uri;
3593 int avl;
3594 int act;
3595 struct sipe_buddy *sbuddy;
3597 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3599 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3600 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3601 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3602 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3603 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3604 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3605 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
3606 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
3608 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3609 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3610 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3611 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3612 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3613 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3615 name = xmlnode_get_attrib(xn_presentity, "uri");
3616 uri = g_strdup_printf("sip:%s", name);
3617 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3618 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3620 // updating display name if alias was just URI
3621 if (xn_display_name) {
3622 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3623 GSList *entry = buddies;
3624 PurpleBuddy *p_buddy;
3625 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3627 while (entry) {
3628 const char *email_str, *server_alias;
3630 p_buddy = entry->data;
3632 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3633 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3634 purple_blist_alias_buddy(p_buddy, display_name);
3637 server_alias = purple_buddy_get_server_alias(p_buddy);
3638 if (display_name &&
3639 ( (server_alias && strcmp(display_name, server_alias))
3640 || !server_alias || strlen(server_alias) == 0 )
3642 purple_blist_server_alias_buddy(p_buddy, display_name);
3645 if (email) {
3646 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3647 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3648 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3652 entry = entry->next;
3656 avl = atoi(availability);
3657 act = atoi(activity);
3659 if(sip->msrtc_event_categories){
3660 if (act == 100 && avl == 0)
3661 activity_name = SIPE_STATUS_ID_OFFLINE;
3662 else if (act == 100 && avl == 300)
3663 activity_name = SIPE_STATUS_ID_AWAY;
3664 else if (act == 300 && avl == 300)
3665 activity_name = SIPE_STATUS_ID_BRB;
3666 else if (act == 400 && avl == 300)
3667 activity_name = SIPE_STATUS_ID_AVAILABLE;
3668 else if (act == 500 && act == 300)
3669 activity_name = SIPE_STATUS_ID_ONPHONE;
3670 else if (act == 600 && avl == 300)
3671 activity_name = SIPE_STATUS_ID_BUSY;
3672 else if (act == 0 && avl == 0){ //MSRTC elements are zero
3673 if(avail){ //Check for LegacyInterop elements
3674 avl = atoi(avail);
3675 if(avl == 18500)
3676 activity_name = SIPE_STATUS_ID_OFFLINE;
3677 else if (avl == 3500)
3678 activity_name = SIPE_STATUS_ID_AVAILABLE;
3679 else if (avl == 15500)
3680 activity_name = SIPE_STATUS_ID_AWAY;
3681 else if (avl == 6500)
3682 activity_name = SIPE_STATUS_ID_BUSY;
3683 else if (avl == 12500)
3684 activity_name = SIPE_STATUS_ID_BRB;
3689 if(activity_name == NULL){
3690 if (act <= 100)
3691 activity_name = SIPE_STATUS_ID_AWAY;
3692 else if (act <= 150)
3693 activity_name = SIPE_STATUS_ID_LUNCH;
3694 else if (act <= 300)
3695 activity_name = SIPE_STATUS_ID_BRB;
3696 else if (act <= 400)
3697 activity_name = SIPE_STATUS_ID_AVAILABLE;
3698 else if (act <= 500)
3699 activity_name = SIPE_STATUS_ID_ONPHONE;
3700 else if (act <= 600)
3701 activity_name = SIPE_STATUS_ID_BUSY;
3702 else
3703 activity_name = SIPE_STATUS_ID_AVAILABLE;
3705 if (avl == 0)
3706 activity_name = SIPE_STATUS_ID_OFFLINE;
3709 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3710 if (sbuddy)
3712 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3713 sbuddy->annotation = NULL;
3714 if (note) { sbuddy->annotation = g_strdup(note); }
3716 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3717 sbuddy->device_name = NULL;
3718 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3721 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3722 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3723 g_free(note);
3724 xmlnode_free(xn_presentity);
3725 g_free(uri);
3728 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3730 char *ctype = sipmsg_find_header(msg, "Content-Type");
3732 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3734 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3735 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3737 const char *content = msg->body;
3738 unsigned length = msg->bodylen;
3739 PurpleMimeDocument *mime = NULL;
3741 if (strstr(ctype, "multipart"))
3743 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3744 const char *content_type;
3745 GList* parts;
3746 mime = purple_mime_document_parse(doc);
3747 parts = purple_mime_document_get_parts(mime);
3748 while(parts) {
3749 content = purple_mime_part_get_data(parts->data);
3750 length = purple_mime_part_get_length(parts->data);
3751 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3752 if(content_type && strstr(content_type,"application/rlmi+xml"))
3754 process_incoming_notify_rlmi_resub(sip, content, length);
3756 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3758 process_incoming_notify_msrtc(sip, content, length);
3760 else
3762 process_incoming_notify_rlmi(sip, content, length);
3764 parts = parts->next;
3766 g_free(doc);
3768 if (mime)
3770 purple_mime_document_free(mime);
3773 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3775 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3777 else if(strstr(ctype, "application/rlmi+xml"))
3779 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3782 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3784 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3786 else
3788 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3793 * Dispatcher for all incoming subscription information
3794 * whether it comes from NOTIFY, BENOTIFY requests or
3795 * piggy-backed to subscription's OK responce.
3797 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3798 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3800 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3802 gchar *event = sipmsg_find_header(msg, "Event");
3803 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3804 int timeout = 0;
3806 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3807 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3809 if (!request)
3811 const gchar *expires_header;
3812 expires_header = sipmsg_find_header(msg, "Expires");
3813 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3814 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", timeout);
3815 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
3818 if (!subscription_state || strstr(subscription_state, "active"))
3820 if (event && !g_ascii_strcasecmp(event, "presence"))
3822 sipe_process_presence(sip, msg);
3824 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3826 sipe_process_roaming_contacts(sip, msg, NULL);
3828 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
3830 sipe_process_roaming_self(sip, msg);
3832 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3834 sipe_process_roaming_acl(sip, msg);
3836 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3838 sipe_process_presence_wpending(sip, msg);
3840 else
3842 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3846 //The server sends a (BE)NOTIFY with the status 'terminated'
3847 if(request && subscription_state && strstr(subscription_state, "terminated") ) {
3848 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3849 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3850 g_free(from);
3853 if (timeout) {
3854 // For LSC 2005
3855 if (event && !sip->msrtc_event_categories)
3857 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
3858 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3860 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3861 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_wpending, sip, NULL);
3862 g_free(action_name);
3864 else if (!g_ascii_strcasecmp(event, "presence") &&
3865 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3867 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3868 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", who);
3869 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_batched, sip, who);
3870 g_free(action_name);
3871 g_free(who);
3876 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
3878 sipe_process_registration_notify(sip, msg);
3881 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3882 if (request && !benotify)
3884 sipmsg_remove_header(msg, "Expires");
3885 sipmsg_remove_header(msg, "subscription-state");
3886 sipmsg_remove_header(msg, "Event");
3887 sipmsg_remove_header(msg, "Require");
3888 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3893 * unused. Needed?
3895 static gchar* gen_xpidf(struct sipe_account_data *sip)
3897 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3898 "<presence>\r\n"
3899 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3900 "<display name=\"sip:%s\"/>\r\n"
3901 "<atom id=\"1234\">\r\n"
3902 "<address uri=\"sip:%s\">\r\n"
3903 "<status status=\"%s\"/>\r\n"
3904 "</address>\r\n"
3905 "</atom>\r\n"
3906 "</presence>\r\n",
3907 sip->username,
3908 sip->username,
3909 sip->username,
3910 sip->status);
3911 return doc;
3916 static gchar* gen_pidf(struct sipe_account_data *sip)
3918 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3919 "<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"
3920 "<tuple id=\"0\">\r\n"
3921 "<status>\r\n"
3922 "<basic>open</basic>\r\n"
3923 "<ep:activities>\r\n"
3924 " <ep:activity>%s</ep:activity>\r\n"
3925 "</ep:activities>"
3926 "</status>\r\n"
3927 "</tuple>\r\n"
3928 "<ci:display-name>%s</ci:display-name>\r\n"
3929 "</presence>",
3930 sip->username,
3931 sip->status,
3932 sip->username);
3933 return doc;
3937 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
3939 int availability = 300; // online
3940 int activity = 400; // Available
3941 gchar *name;
3942 gchar *body;
3943 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
3944 activity = 100;
3945 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
3946 activity = 150;
3947 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
3948 activity = 300;
3949 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
3950 activity = 400;
3951 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
3952 activity = 500;
3953 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
3954 activity = 600;
3955 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
3956 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
3957 availability = 0; // offline
3958 activity = 100;
3959 } else {
3960 activity = 400; // available
3963 name = g_strdup_printf("sip: sip:%s", sip->username);
3964 //@TODO: send user data - state; add hostname in upper case
3965 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3966 send_soap_request_with_cb(sip, body, NULL , NULL);
3967 g_free(name);
3968 g_free(body);
3971 static gboolean
3972 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3974 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3975 if (msg->response == 200) {
3976 sip->status_version = 0;
3977 send_presence_status(sip);
3979 return TRUE;
3982 static gboolean
3983 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3985 if (msg->response == 409) {
3986 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3987 // TODO need to parse the version #'s?
3988 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3989 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3990 gchar *tmp;
3991 gchar *hdr;
3993 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
3995 tmp = get_contact(sip);
3996 hdr = g_strdup_printf("Contact: %s\r\n"
3997 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3999 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4001 g_free(tmp);
4002 g_free(hdr);
4003 g_free(uri);
4004 g_free(doc);
4006 return TRUE;
4009 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4011 int code;
4012 gchar *uri;
4013 gchar *doc;
4014 gchar *tmp;
4015 gchar *hdr;
4016 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4017 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4018 code = 12000;
4019 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4020 code = 9000;
4021 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4022 code = 7500;
4023 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4024 code = 6000;
4025 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4026 code = 4500;
4027 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4028 code = 3000;
4029 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4030 code = 0;
4031 } else {
4032 // Offline or invisible
4033 code = 18000;
4036 uri = g_strdup_printf("sip:%s", sip->username);
4037 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4038 sip->status_version, code,
4039 sip->status_version, code,
4040 sip->status_version, note ? note : "",
4041 sip->status_version, note ? note : "",
4042 sip->status_version, note ? note : ""
4044 sip->status_version++;
4046 tmp = get_contact(sip);
4047 hdr = g_strdup_printf("Contact: %s\r\n"
4048 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4050 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4052 g_free(tmp);
4053 g_free(hdr);
4054 g_free(uri);
4055 g_free(doc);
4058 static void send_presence_status(struct sipe_account_data *sip)
4060 PurpleStatus * status = purple_account_get_active_status(sip->account);
4061 const gchar *note;
4062 if (!status) return;
4064 note = purple_status_get_attr_string(status, "message");
4066 if(sip->msrtc_event_categories){
4067 send_presence_category_publish(sip, note);
4068 } else {
4069 send_presence_soap(sip, note);
4073 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4075 gboolean found = FALSE;
4076 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4077 if (msg->response == 0) { /* request */
4078 if (!strcmp(msg->method, "MESSAGE")) {
4079 process_incoming_message(sip, msg);
4080 found = TRUE;
4081 } else if (!strcmp(msg->method, "NOTIFY")) {
4082 purple_debug_info("sipe","send->process_incoming_notify\n");
4083 process_incoming_notify(sip, msg, TRUE, FALSE);
4084 found = TRUE;
4085 } else if (!strcmp(msg->method, "BENOTIFY")) {
4086 purple_debug_info("sipe","send->process_incoming_benotify\n");
4087 process_incoming_notify(sip, msg, TRUE, TRUE);
4088 found = TRUE;
4089 } else if (!strcmp(msg->method, "INVITE")) {
4090 process_incoming_invite(sip, msg);
4091 found = TRUE;
4092 } else if (!strcmp(msg->method, "OPTIONS")) {
4093 process_incoming_options(sip, msg);
4094 found = TRUE;
4095 } else if (!strcmp(msg->method, "INFO")) {
4096 // TODO needs work
4097 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4098 if (from) {
4099 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4101 g_free(from);
4102 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4103 found = TRUE;
4104 } else if (!strcmp(msg->method, "ACK")) {
4105 // ACK's don't need any response
4106 found = TRUE;
4107 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4108 // LCS 2005 sends us these - just respond 200 OK
4109 found = TRUE;
4110 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4111 } else if (!strcmp(msg->method, "BYE")) {
4112 struct sip_im_session *session;
4113 gchar *from;
4114 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4116 from = parse_from(sipmsg_find_header(msg, "From"));
4117 session = find_im_session (sip, from);
4118 g_free(from);
4120 if (session) {
4121 // TODO Let the user know the other user left the conversation?
4122 im_session_destroy(sip, session);
4125 found = TRUE;
4126 } else {
4127 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4129 } else { /* response */
4130 struct transaction *trans = transactions_find(sip, msg);
4131 if (trans) {
4132 if (msg->response == 407) {
4133 gchar *resend, *auth, *ptmp;
4135 if (sip->proxy.retries > 30) return;
4136 sip->proxy.retries++;
4137 /* do proxy authentication */
4139 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4141 fill_auth(sip, ptmp, &sip->proxy);
4142 auth = auth_header(sip, &sip->proxy, trans->msg);
4143 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4144 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4145 g_free(auth);
4146 resend = sipmsg_to_string(trans->msg);
4147 /* resend request */
4148 sendout_pkt(sip->gc, resend);
4149 g_free(resend);
4150 } else {
4151 if (msg->response == 100 || msg->response == 180) {
4152 /* ignore provisional response */
4153 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4154 } else {
4155 sip->proxy.retries = 0;
4156 if (!strcmp(trans->msg->method, "REGISTER")) {
4157 if (msg->response == 401)
4159 sip->registrar.retries++;
4160 sip->registrar.expires = 0;
4162 else
4164 sip->registrar.retries = 0;
4166 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4167 } else {
4168 if (msg->response == 401) {
4169 gchar *resend, *auth, *ptmp;
4171 if (sip->registrar.retries > 4) return;
4172 sip->registrar.retries++;
4174 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4175 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4176 } else {
4177 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4180 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4182 fill_auth(sip, ptmp, &sip->registrar);
4183 auth = auth_header(sip, &sip->registrar, trans->msg);
4184 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4185 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
4187 //sipmsg_remove_header(trans->msg, "Authorization");
4188 //sipmsg_add_header(trans->msg, "Authorization", auth);
4189 g_free(auth);
4190 resend = sipmsg_to_string(trans->msg);
4191 /* resend request */
4192 sendout_pkt(sip->gc, resend);
4193 g_free(resend);
4197 if (trans->callback) {
4198 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4199 /* call the callback to process response*/
4200 (trans->callback)(sip, msg, trans);
4202 /* Not sure if this is needed or what needs to be done
4203 but transactions seem to be removed prematurely so
4204 this only removes them if the response is 200 OK */
4205 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4206 /*Has a bug and it's unneccesary*/
4207 /*transactions_remove(sip, trans);*/
4211 found = TRUE;
4212 } else {
4213 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4216 if (!found) {
4217 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4221 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4223 char *cur;
4224 char *dummy;
4225 struct sipmsg *msg;
4226 int restlen;
4227 cur = conn->inbuf;
4229 /* according to the RFC remove CRLF at the beginning */
4230 while (*cur == '\r' || *cur == '\n') {
4231 cur++;
4233 if (cur != conn->inbuf) {
4234 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4235 conn->inbufused = strlen(conn->inbuf);
4238 /* Received a full Header? */
4239 sip->processing_input = TRUE;
4240 while (sip->processing_input &&
4241 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4242 time_t currtime = time(NULL);
4243 cur += 2;
4244 cur[0] = '\0';
4245 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4246 msg = sipmsg_parse_header(conn->inbuf);
4247 cur[0] = '\r';
4248 cur += 2;
4249 restlen = conn->inbufused - (cur - conn->inbuf);
4250 if (restlen >= msg->bodylen) {
4251 dummy = g_malloc(msg->bodylen + 1);
4252 memcpy(dummy, cur, msg->bodylen);
4253 dummy[msg->bodylen] = '\0';
4254 msg->body = dummy;
4255 cur += msg->bodylen;
4256 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4257 conn->inbufused = strlen(conn->inbuf);
4258 } else {
4259 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4260 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4261 sipmsg_free(msg);
4262 return;
4265 /*if (msg->body) {
4266 purple_debug_info("sipe", "body:\n%s", msg->body);
4269 // Verify the signature before processing it
4270 if (sip->registrar.ntlm_key) {
4271 struct sipmsg_breakdown msgbd;
4272 gchar *signature_input_str;
4273 gchar *signature = NULL;
4274 gchar *rspauth;
4275 msgbd.msg = msg;
4276 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4277 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4278 if (signature_input_str != NULL) {
4279 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4281 g_free(signature_input_str);
4283 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4285 if (signature != NULL) {
4286 if (rspauth != NULL) {
4287 if (purple_ntlm_verify_signature (signature, rspauth)) {
4288 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4289 process_input_message(sip, msg);
4290 } else {
4291 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4292 purple_connection_error(sip->gc, _("Invalid message signature received"));
4293 sip->gc->wants_to_die = TRUE;
4295 } else if (msg->response == 401) {
4296 purple_connection_error(sip->gc, _("Wrong Password"));
4297 sip->gc->wants_to_die = TRUE;
4299 g_free(signature);
4302 g_free(rspauth);
4303 sipmsg_breakdown_free(&msgbd);
4304 } else {
4305 process_input_message(sip, msg);
4308 sipmsg_free(msg);
4312 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4314 PurpleConnection *gc = data;
4315 struct sipe_account_data *sip = gc->proto_data;
4316 struct sipmsg *msg;
4317 int len;
4318 time_t currtime;
4320 static char buffer[65536];
4321 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4322 buffer[len] = '\0';
4323 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4324 msg = sipmsg_parse_msg(buffer);
4325 if (msg) process_input_message(sip, msg);
4329 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4331 struct sipe_account_data *sip = gc->proto_data;
4332 PurpleSslConnection *gsc = sip->gsc;
4334 purple_debug_error("sipe", "%s",debug);
4335 purple_connection_error(gc, msg);
4337 /* Invalidate this connection. Next send will open a new one */
4338 if (gsc) {
4339 connection_remove(sip, gsc->fd);
4340 purple_ssl_close(gsc);
4342 sip->gsc = NULL;
4343 sip->fd = -1;
4346 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4348 PurpleConnection *gc = data;
4349 struct sipe_account_data *sip;
4350 struct sip_connection *conn;
4351 int readlen, len;
4352 gboolean firstread = TRUE;
4354 /* NOTE: This check *IS* necessary */
4355 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4356 purple_ssl_close(gsc);
4357 return;
4360 sip = gc->proto_data;
4361 conn = connection_find(sip, gsc->fd);
4362 if (conn == NULL) {
4363 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4364 gc->wants_to_die = TRUE;
4365 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4366 return;
4369 /* Read all available data from the SSL connection */
4370 do {
4371 /* Increase input buffer size as needed */
4372 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4373 conn->inbuflen += SIMPLE_BUF_INC;
4374 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4375 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4378 /* Try to read as much as there is space left in the buffer */
4379 readlen = conn->inbuflen - conn->inbufused - 1;
4380 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4382 if (len < 0 && errno == EAGAIN) {
4383 /* Try again later */
4384 return;
4385 } else if (len < 0) {
4386 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4387 return;
4388 } else if (firstread && (len == 0)) {
4389 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4390 return;
4393 conn->inbufused += len;
4394 firstread = FALSE;
4396 /* Equivalence indicates that there is possibly more data to read */
4397 } while (len == readlen);
4399 conn->inbuf[conn->inbufused] = '\0';
4400 process_input(sip, conn);
4404 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4406 PurpleConnection *gc = data;
4407 struct sipe_account_data *sip = gc->proto_data;
4408 int len;
4409 struct sip_connection *conn = connection_find(sip, source);
4410 if (!conn) {
4411 purple_debug_error("sipe", "Connection not found!\n");
4412 return;
4415 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4416 conn->inbuflen += SIMPLE_BUF_INC;
4417 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4420 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4422 if (len < 0 && errno == EAGAIN)
4423 return;
4424 else if (len <= 0) {
4425 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4426 connection_remove(sip, source);
4427 if (sip->fd == source) sip->fd = -1;
4428 return;
4431 conn->inbufused += len;
4432 conn->inbuf[conn->inbufused] = '\0';
4434 process_input(sip, conn);
4437 /* Callback for new connections on incoming TCP port */
4438 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4440 PurpleConnection *gc = data;
4441 struct sipe_account_data *sip = gc->proto_data;
4442 struct sip_connection *conn;
4444 int newfd = accept(source, NULL, NULL);
4446 conn = connection_create(sip, newfd);
4448 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4451 static void login_cb(gpointer data, gint source, const gchar *error_message)
4453 PurpleConnection *gc = data;
4454 struct sipe_account_data *sip;
4455 struct sip_connection *conn;
4457 if (!PURPLE_CONNECTION_IS_VALID(gc))
4459 if (source >= 0)
4460 close(source);
4461 return;
4464 if (source < 0) {
4465 purple_connection_error(gc, _("Could not connect"));
4466 return;
4469 sip = gc->proto_data;
4470 sip->fd = source;
4471 sip->last_keepalive = time(NULL);
4473 conn = connection_create(sip, source);
4475 do_register(sip);
4477 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4480 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4482 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4483 if (sip == NULL) return;
4485 do_register(sip);
4488 static guint sipe_ht_hash_nick(const char *nick)
4490 char *lc = g_utf8_strdown(nick, -1);
4491 guint bucket = g_str_hash(lc);
4492 g_free(lc);
4494 return bucket;
4497 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4499 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4502 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4504 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4506 sip->listen_data = NULL;
4508 if (listenfd == -1) {
4509 purple_connection_error(sip->gc, _("Could not create listen socket"));
4510 return;
4513 sip->fd = listenfd;
4515 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4516 sip->listenfd = sip->fd;
4518 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4520 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4521 do_register(sip);
4524 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4526 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4527 int addr_size;
4529 sip->query_data = NULL;
4531 if (!hosts || !hosts->data) {
4532 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4533 return;
4536 addr_size = GPOINTER_TO_INT(hosts->data);
4537 hosts = g_slist_remove(hosts, hosts->data);
4538 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4539 g_free(hosts->data);
4540 hosts = g_slist_remove(hosts, hosts->data);
4541 while (hosts) {
4542 hosts = g_slist_remove(hosts, hosts->data);
4543 g_free(hosts->data);
4544 hosts = g_slist_remove(hosts, hosts->data);
4547 /* create socket for incoming connections */
4548 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4549 sipe_udp_host_resolved_listen_cb, sip);
4550 if (sip->listen_data == NULL) {
4551 purple_connection_error(sip->gc, _("Could not create listen socket"));
4552 return;
4556 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4557 gpointer data)
4559 PurpleConnection *gc = data;
4560 struct sipe_account_data *sip;
4562 /* If the connection is already disconnected, we don't need to do anything else */
4563 if (!PURPLE_CONNECTION_IS_VALID(gc))
4564 return;
4566 sip = gc->proto_data;
4567 sip->fd = -1;
4568 sip->gsc = NULL;
4570 switch(error) {
4571 case PURPLE_SSL_CONNECT_FAILED:
4572 purple_connection_error(gc, _("Connection Failed"));
4573 break;
4574 case PURPLE_SSL_HANDSHAKE_FAILED:
4575 purple_connection_error(gc, _("SSL Handshake Failed"));
4576 break;
4577 case PURPLE_SSL_CERTIFICATE_INVALID:
4578 purple_connection_error(gc, _("SSL Certificate Invalid"));
4579 break;
4583 static void
4584 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4586 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4587 PurpleProxyConnectData *connect_data;
4589 sip->listen_data = NULL;
4591 sip->listenfd = listenfd;
4592 if (sip->listenfd == -1) {
4593 purple_connection_error(sip->gc, _("Could not create listen socket"));
4594 return;
4597 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4598 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4599 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4600 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4601 sipe_newconn_cb, sip->gc);
4602 purple_debug_info("sipe", "connecting to %s port %d\n",
4603 sip->realhostname, sip->realport);
4604 /* open tcp connection to the server */
4605 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4606 sip->realport, login_cb, sip->gc);
4608 if (connect_data == NULL) {
4609 purple_connection_error(sip->gc, _("Couldn't create socket"));
4614 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4616 PurpleAccount *account = sip->account;
4617 PurpleConnection *gc = sip->gc;
4619 if (purple_account_get_bool(account, "useport", FALSE)) {
4620 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4621 port = purple_account_get_int(account, "port", 0);
4622 } else {
4623 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4626 sip->realhostname = hostname;
4627 sip->realport = port;
4629 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4630 hostname, port);
4632 /* TODO: is there a good default grow size? */
4633 if (sip->transport != SIPE_TRANSPORT_UDP)
4634 sip->txbuf = purple_circ_buffer_new(0);
4636 if (sip->transport == SIPE_TRANSPORT_TLS) {
4637 /* SSL case */
4638 if (!purple_ssl_is_supported()) {
4639 gc->wants_to_die = TRUE;
4640 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4641 return;
4644 purple_debug_info("sipe", "using SSL\n");
4646 sip->gsc = purple_ssl_connect(account, hostname, port,
4647 login_cb_ssl, sipe_ssl_connect_failure, gc);
4648 if (sip->gsc == NULL) {
4649 purple_connection_error(gc, _("Could not create SSL context"));
4650 return;
4652 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4653 /* UDP case */
4654 purple_debug_info("sipe", "using UDP\n");
4656 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4657 if (sip->query_data == NULL) {
4658 purple_connection_error(gc, _("Could not resolve hostname"));
4660 } else {
4661 /* TCP case */
4662 purple_debug_info("sipe", "using TCP\n");
4663 /* create socket for incoming connections */
4664 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4665 sipe_tcp_connect_listen_cb, sip);
4666 if (sip->listen_data == NULL) {
4667 purple_connection_error(gc, _("Could not create listen socket"));
4668 return;
4673 /* Service list for autodection */
4674 static const struct sipe_service_data service_autodetect[] = {
4675 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4676 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4677 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4678 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4679 { NULL, NULL, 0 }
4682 /* Service list for SSL/TLS */
4683 static const struct sipe_service_data service_tls[] = {
4684 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4685 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4686 { NULL, NULL, 0 }
4689 /* Service list for TCP */
4690 static const struct sipe_service_data service_tcp[] = {
4691 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4692 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4693 { NULL, NULL, 0 }
4696 /* Service list for UDP */
4697 static const struct sipe_service_data service_udp[] = {
4698 { "sip", "udp", SIPE_TRANSPORT_UDP },
4699 { NULL, NULL, 0 }
4702 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4703 static void resolve_next_service(struct sipe_account_data *sip,
4704 const struct sipe_service_data *start)
4706 if (start) {
4707 sip->service_data = start;
4708 } else {
4709 sip->service_data++;
4710 if (sip->service_data->service == NULL) {
4711 gchar *hostname;
4712 /* Try connecting to the SIP hostname directly */
4713 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4714 if (sip->auto_transport) {
4715 // If SSL is supported, default to using it; OCS servers aren't configured
4716 // by default to accept TCP
4717 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4718 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4719 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4722 hostname = g_strdup(sip->sipdomain);
4723 create_connection(sip, hostname, 0);
4724 return;
4728 /* Try to resolve next service */
4729 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4730 sip->service_data->transport,
4731 sip->sipdomain,
4732 srvresolved, sip);
4735 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4737 struct sipe_account_data *sip = data;
4739 sip->srv_query_data = NULL;
4741 /* find the host to connect to */
4742 if (results) {
4743 gchar *hostname = g_strdup(resp->hostname);
4744 int port = resp->port;
4745 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4746 hostname, port);
4747 g_free(resp);
4749 sip->transport = sip->service_data->type;
4751 create_connection(sip, hostname, port);
4752 } else {
4753 resolve_next_service(sip, NULL);
4757 static void sipe_login(PurpleAccount *account)
4759 PurpleConnection *gc;
4760 struct sipe_account_data *sip;
4761 gchar **signinname_login, **userserver, **domain_user;
4762 const char *transport;
4764 const char *username = purple_account_get_username(account);
4765 gc = purple_account_get_connection(account);
4767 if (strpbrk(username, "\t\v\r\n") != NULL) {
4768 gc->wants_to_die = TRUE;
4769 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
4770 return;
4773 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4774 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4775 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4776 sip->gc = gc;
4777 sip->account = account;
4778 sip->reregister_set = FALSE;
4779 sip->reauthenticate_set = FALSE;
4780 sip->subscribed = FALSE;
4781 sip->subscribed_buddies = FALSE;
4783 signinname_login = g_strsplit(username, ",", 2);
4785 userserver = g_strsplit(signinname_login[0], "@", 2);
4786 purple_connection_set_display_name(gc, userserver[0]);
4787 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4788 sip->sipdomain = g_strdup(userserver[1]);
4790 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
4791 gc->wants_to_die = TRUE;
4792 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4793 return;
4796 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4797 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4798 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4800 sip->password = g_strdup(purple_connection_get_password(gc));
4802 g_strfreev(userserver);
4803 g_strfreev(domain_user);
4804 g_strfreev(signinname_login);
4806 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4808 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4810 /* TODO: Set the status correctly. */
4811 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4813 transport = purple_account_get_string(account, "transport", "auto");
4814 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4815 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4816 SIPE_TRANSPORT_UDP;
4818 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4819 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4820 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4821 } else if (strcmp(transport, "auto") == 0) {
4822 sip->auto_transport = TRUE;
4823 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4824 } else if (strcmp(transport, "tls") == 0) {
4825 resolve_next_service(sip, service_tls);
4826 } else if (strcmp(transport, "tcp") == 0) {
4827 resolve_next_service(sip, service_tcp);
4828 } else {
4829 resolve_next_service(sip, service_udp);
4833 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4835 connection_free_all(sip);
4837 g_free(sip->epid);
4838 sip->epid = NULL;
4840 if (sip->query_data != NULL)
4841 purple_dnsquery_destroy(sip->query_data);
4842 sip->query_data = NULL;
4844 if (sip->srv_query_data != NULL)
4845 purple_srv_cancel(sip->srv_query_data);
4846 sip->srv_query_data = NULL;
4848 if (sip->listen_data != NULL)
4849 purple_network_listen_cancel(sip->listen_data);
4850 sip->listen_data = NULL;
4852 if (sip->gsc != NULL)
4853 purple_ssl_close(sip->gsc);
4854 sip->gsc = NULL;
4856 sipe_auth_free(&sip->registrar);
4857 sipe_auth_free(&sip->proxy);
4859 if (sip->txbuf)
4860 purple_circ_buffer_destroy(sip->txbuf);
4861 sip->txbuf = NULL;
4863 g_free(sip->realhostname);
4864 sip->realhostname = NULL;
4866 if (sip->listenpa)
4867 purple_input_remove(sip->listenpa);
4868 sip->listenpa = 0;
4869 if (sip->tx_handler)
4870 purple_input_remove(sip->tx_handler);
4871 sip->tx_handler = 0;
4872 if (sip->resendtimeout)
4873 purple_timeout_remove(sip->resendtimeout);
4874 sip->resendtimeout = 0;
4875 if (sip->timeouts) {
4876 GSList *entry = sip->timeouts;
4877 while (entry) {
4878 struct scheduled_action *sched_action = entry->data;
4879 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4880 purple_timeout_remove(sched_action->timeout_handler);
4881 g_free(sched_action->payload);
4882 g_free(sched_action->name);
4883 g_free(sched_action);
4884 entry = entry->next;
4887 g_slist_free(sip->timeouts);
4889 if (sip->allow_events) {
4890 GSList *entry = sip->allow_events;
4891 while (entry) {
4892 g_free(entry->data);
4893 entry = entry->next;
4896 g_slist_free(sip->allow_events);
4898 if (sip->contact)
4899 g_free(sip->contact);
4900 sip->contact = NULL;
4901 if (sip->regcallid)
4902 g_free(sip->regcallid);
4903 sip->regcallid = NULL;
4905 sip->fd = -1;
4906 sip->processing_input = FALSE;
4910 * A callback for g_hash_table_foreach_remove
4912 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4914 sipe_free_buddy(buddy);
4917 static void sipe_close(PurpleConnection *gc)
4919 struct sipe_account_data *sip = gc->proto_data;
4921 if (sip) {
4922 /* leave all conversations */
4923 im_session_close_all(sip);
4925 /* unregister */
4926 do_register_exp(sip, 0);
4928 sipe_connection_cleanup(sip);
4929 g_free(sip->sipdomain);
4930 g_free(sip->username);
4931 g_free(sip->password);
4932 g_free(sip->authdomain);
4933 g_free(sip->authuser);
4934 g_free(sip->status);
4936 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4937 g_hash_table_destroy(sip->buddies);
4939 g_free(gc->proto_data);
4940 gc->proto_data = NULL;
4943 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4945 PurpleAccount *acct = purple_connection_get_account(gc);
4946 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4947 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4948 if (conv == NULL)
4949 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4950 purple_conversation_present(conv);
4951 g_free(id);
4954 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4957 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4958 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4961 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4963 PurpleNotifySearchResults *results;
4964 PurpleNotifySearchColumn *column;
4965 xmlnode *searchResults;
4966 xmlnode *mrow;
4967 int match_count = 0;
4968 gboolean more = FALSE;
4969 gchar *secondary;
4971 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
4973 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4974 if (!searchResults) {
4975 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4976 return FALSE;
4979 results = purple_notify_searchresults_new();
4981 if (results == NULL) {
4982 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4983 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4985 xmlnode_free(searchResults);
4986 return FALSE;
4989 column = purple_notify_searchresults_column_new(_("User Name"));
4990 purple_notify_searchresults_column_add(results, column);
4992 column = purple_notify_searchresults_column_new(_("Name"));
4993 purple_notify_searchresults_column_add(results, column);
4995 column = purple_notify_searchresults_column_new(_("Company"));
4996 purple_notify_searchresults_column_add(results, column);
4998 column = purple_notify_searchresults_column_new(_("Country"));
4999 purple_notify_searchresults_column_add(results, column);
5001 column = purple_notify_searchresults_column_new(_("Email"));
5002 purple_notify_searchresults_column_add(results, column);
5004 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5005 GList *row = NULL;
5007 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5008 row = g_list_append(row, g_strdup(uri_parts[1]));
5009 g_strfreev(uri_parts);
5011 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5012 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5013 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5014 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5016 purple_notify_searchresults_row_add(results, row);
5017 match_count++;
5020 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5021 char *data = xmlnode_get_data_unescaped(mrow);
5022 more = (g_strcasecmp(data, "true") == 0);
5023 g_free(data);
5026 secondary = g_strdup_printf(
5027 dngettext(GETTEXT_PACKAGE,
5028 "Found %d contact%s:",
5029 "Found %d contacts%s:", match_count),
5030 match_count, more ? _(" (more matched your query)") : "");
5032 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5033 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5034 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5036 g_free(secondary);
5037 xmlnode_free(searchResults);
5038 return TRUE;
5041 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5043 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5044 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5045 unsigned i = 0;
5047 do {
5048 PurpleRequestField *field = entries->data;
5049 const char *id = purple_request_field_get_id(field);
5050 const char *value = purple_request_field_string_get_value(field);
5052 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5054 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5055 } while ((entries = g_list_next(entries)) != NULL);
5056 attrs[i] = NULL;
5058 if (i > 0) {
5059 gchar *query = g_strjoinv(NULL, attrs);
5060 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5061 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5062 send_soap_request_with_cb(gc->proto_data, body,
5063 (TransCallback) process_search_contact_response, NULL);
5064 g_free(body);
5065 g_free(query);
5068 g_strfreev(attrs);
5071 static void sipe_show_find_contact(PurplePluginAction *action)
5073 PurpleConnection *gc = (PurpleConnection *) action->context;
5074 PurpleRequestFields *fields;
5075 PurpleRequestFieldGroup *group;
5076 PurpleRequestField *field;
5078 fields = purple_request_fields_new();
5079 group = purple_request_field_group_new(NULL);
5080 purple_request_fields_add_group(fields, group);
5082 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5083 purple_request_field_group_add_field(group, field);
5084 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5085 purple_request_field_group_add_field(group, field);
5086 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5087 purple_request_field_group_add_field(group, field);
5088 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5089 purple_request_field_group_add_field(group, field);
5091 purple_request_fields(gc,
5092 _("Search"),
5093 _("Search for a Contact"),
5094 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5095 fields,
5096 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5097 _("_Cancel"), NULL,
5098 purple_connection_get_account(gc), NULL, NULL, gc);
5101 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5103 GList *menu = NULL;
5104 PurplePluginAction *act;
5106 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5107 menu = g_list_prepend(menu, act);
5109 menu = g_list_reverse(menu);
5111 return menu;
5114 static void dummy_permit_deny(PurpleConnection *gc)
5118 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5120 return TRUE;
5124 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5126 return TRUE;
5130 static char *sipe_status_text(PurpleBuddy *buddy)
5132 struct sipe_account_data *sip;
5133 struct sipe_buddy *sbuddy;
5134 char *text = NULL;
5136 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5137 if (sip) //happens on pidgin exit
5139 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5140 if (sbuddy && sbuddy->annotation)
5142 text = g_strdup(sbuddy->annotation);
5146 return text;
5149 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5151 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5152 const PurpleStatus *status = purple_presence_get_active_status(presence);
5153 struct sipe_account_data *sip;
5154 struct sipe_buddy *sbuddy;
5155 char *annotation = NULL;
5157 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5158 if (sip) //happens on pidgin exit
5160 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5161 if (sbuddy)
5163 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5167 //Layout
5168 if (purple_presence_is_online(presence))
5170 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5173 if (annotation)
5175 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5176 g_free(annotation);
5181 static GHashTable *
5182 sipe_get_account_text_table(PurpleAccount *account)
5184 GHashTable *table;
5185 table = g_hash_table_new(g_str_hash, g_str_equal);
5186 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5187 return table;
5190 static PurpleBuddy *
5191 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5193 PurpleBuddy *clone;
5194 const gchar *server_alias, *email;
5195 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5197 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5199 purple_blist_add_buddy(clone, NULL, group, NULL);
5201 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5202 if (server_alias) {
5203 purple_blist_server_alias_buddy(clone, server_alias);
5206 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5207 if (email) {
5208 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5211 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5212 //for UI to update;
5213 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5214 return clone;
5217 static void
5218 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5220 PurpleBuddy *buddy, *b;
5221 PurpleConnection *gc;
5222 PurpleGroup * group = purple_find_group(group_name);
5224 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5226 buddy = (PurpleBuddy *)node;
5228 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5229 gc = purple_account_get_connection(buddy->account);
5231 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5232 if (!b){
5233 b = purple_blist_add_buddy_clone(group, buddy);
5236 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5239 static void
5240 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5242 const gchar *email;
5243 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5245 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5246 if (email)
5248 char *mailto = g_strdup_printf("mailto:%s", email);
5249 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5250 #ifndef _WIN32
5252 pid_t pid;
5253 char *const parmList[] = {mailto, NULL};
5254 if ((pid = fork()) == -1)
5256 purple_debug_info("sipe", "fork() error\n");
5258 else if (pid == 0)
5260 execvp("xdg-email", parmList);
5261 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5264 #else
5266 BOOL ret;
5267 _flushall();
5268 errno = 0;
5269 //@TODO resolve env variable %WINDIR% first
5270 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5271 if (errno)
5273 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5276 #endif
5278 g_free(mailto);
5280 else
5282 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5287 * A menu which appear when right-clicking on buddy in contact list.
5289 static GList *
5290 sipe_buddy_menu(PurpleBuddy *buddy)
5292 PurpleBlistNode *g_node;
5293 PurpleGroup *group, *gr_parent;
5294 PurpleMenuAction *act;
5295 GList *menu = NULL;
5296 GList *menu_groups = NULL;
5298 act = purple_menu_action_new(_("Send Email..."),
5299 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5300 NULL, NULL);
5301 menu = g_list_prepend(menu, act);
5303 gr_parent = purple_buddy_get_group(buddy);
5304 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5305 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5306 continue;
5308 group = (PurpleGroup *)g_node;
5309 if (group == gr_parent)
5310 continue;
5312 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5313 continue;
5315 act = purple_menu_action_new(purple_group_get_name(group),
5316 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5317 group->name, NULL);
5318 menu_groups = g_list_prepend(menu_groups, act);
5320 menu_groups = g_list_reverse(menu_groups);
5322 act = purple_menu_action_new(_("Copy to"),
5323 NULL,
5324 NULL, menu_groups);
5325 menu = g_list_prepend(menu, act);
5326 menu = g_list_reverse(menu);
5328 return menu;
5331 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5332 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5333 return sipe_buddy_menu((PurpleBuddy *) node);
5334 } else {
5335 return NULL;
5339 static gboolean
5340 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5342 gboolean ret = TRUE;
5343 char *username = (char *)trans->payload;
5345 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5346 PurpleBuddy *pbuddy;
5347 struct sipe_buddy *sbuddy;
5348 const char *alias;
5349 char *server_alias = NULL;
5350 char *email = NULL;
5351 const char *device_name = NULL;
5353 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5355 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5356 alias = purple_buddy_get_local_alias(pbuddy);
5358 if (sip)
5360 //will query buddy UA's capabilities and send answer to log
5361 sipe_options_request(sip, username);
5363 sbuddy = g_hash_table_lookup(sip->buddies, username);
5364 if (sbuddy)
5366 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5370 if (msg->response != 200) {
5371 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5372 } else {
5373 xmlnode *searchResults;
5374 xmlnode *mrow;
5376 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5377 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5378 if (!searchResults) {
5379 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5380 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5381 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5382 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5383 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5384 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5385 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5386 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5387 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5388 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5389 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5390 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5391 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5392 if (!email || strcmp("", email)) {
5393 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5394 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5398 xmlnode_free(searchResults);
5401 purple_notify_user_info_add_section_break(info);
5403 if (!server_alias || !strcmp("", server_alias)) {
5404 g_free(server_alias);
5405 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5406 if (server_alias) {
5407 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5411 // same as server alias, do not present
5412 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5413 if (alias)
5415 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5418 if (!email || !strcmp("", email)) {
5419 g_free(email);
5420 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5421 if (email) {
5422 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5426 if (device_name)
5428 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5431 /* show a buddy's user info in a nice dialog box */
5432 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5433 username, /* buddy's username */
5434 info, /* body */
5435 NULL, /* callback called when dialog closed */
5436 NULL); /* userdata for callback */
5438 return ret;
5442 * AD search first, LDAP based
5444 static void sipe_get_info(PurpleConnection *gc, const char *username)
5446 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5447 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5449 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5450 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5451 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5452 g_free(body);
5453 g_free(row);
5456 static PurplePlugin *my_protocol = NULL;
5458 static PurplePluginProtocolInfo prpl_info =
5461 NULL, /* user_splits */
5462 NULL, /* protocol_options */
5463 NO_BUDDY_ICONS, /* icon_spec */
5464 sipe_list_icon, /* list_icon */
5465 NULL, /* list_emblems */
5466 sipe_status_text, /* status_text */
5467 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5468 sipe_status_types, /* away_states */
5469 sipe_blist_node_menu, /* blist_node_menu */
5470 NULL, /* chat_info */
5471 NULL, /* chat_info_defaults */
5472 sipe_login, /* login */
5473 sipe_close, /* close */
5474 sipe_im_send, /* send_im */
5475 NULL, /* set_info */ // TODO maybe
5476 sipe_send_typing, /* send_typing */
5477 sipe_get_info, /* get_info */
5478 sipe_set_status, /* set_status */
5479 NULL, /* set_idle */
5480 NULL, /* change_passwd */
5481 sipe_add_buddy, /* add_buddy */
5482 NULL, /* add_buddies */
5483 sipe_remove_buddy, /* remove_buddy */
5484 NULL, /* remove_buddies */
5485 sipe_add_permit, /* add_permit */
5486 sipe_add_deny, /* add_deny */
5487 sipe_add_deny, /* rem_permit */
5488 sipe_add_permit, /* rem_deny */
5489 dummy_permit_deny, /* set_permit_deny */
5490 NULL, /* join_chat */
5491 NULL, /* reject_chat */
5492 NULL, /* get_chat_name */
5493 NULL, /* chat_invite */
5494 NULL, /* chat_leave */
5495 NULL, /* chat_whisper */
5496 NULL, /* chat_send */
5497 sipe_keep_alive, /* keepalive */
5498 NULL, /* register_user */
5499 NULL, /* get_cb_info */ // deprecated
5500 NULL, /* get_cb_away */ // deprecated
5501 sipe_alias_buddy, /* alias_buddy */
5502 sipe_group_buddy, /* group_buddy */
5503 sipe_rename_group, /* rename_group */
5504 NULL, /* buddy_free */
5505 sipe_convo_closed, /* convo_closed */
5506 purple_normalize_nocase, /* normalize */
5507 NULL, /* set_buddy_icon */
5508 sipe_remove_group, /* remove_group */
5509 NULL, /* get_cb_real_name */ // TODO?
5510 NULL, /* set_chat_topic */
5511 NULL, /* find_blist_chat */
5512 NULL, /* roomlist_get_list */
5513 NULL, /* roomlist_cancel */
5514 NULL, /* roomlist_expand_category */
5515 NULL, /* can_receive_file */
5516 NULL, /* send_file */
5517 NULL, /* new_xfer */
5518 NULL, /* offline_message */
5519 NULL, /* whiteboard_prpl_ops */
5520 sipe_send_raw, /* send_raw */
5521 NULL, /* roomlist_room_serialize */
5522 NULL, /* unregister_user */
5523 NULL, /* send_attention */
5524 NULL, /* get_attention_types */
5526 sizeof(PurplePluginProtocolInfo), /* struct_size */
5527 sipe_get_account_text_table, /* get_account_text_table */
5531 static PurplePluginInfo info = {
5532 PURPLE_PLUGIN_MAGIC,
5533 PURPLE_MAJOR_VERSION,
5534 PURPLE_MINOR_VERSION,
5535 PURPLE_PLUGIN_PROTOCOL, /**< type */
5536 NULL, /**< ui_requirement */
5537 0, /**< flags */
5538 NULL, /**< dependencies */
5539 PURPLE_PRIORITY_DEFAULT, /**< priority */
5540 "prpl-sipe", /**< id */
5541 "Microsoft LCS/OCS", /**< name */
5542 VERSION, /**< version */
5543 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5544 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5545 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5546 "Gabriel Burt <gburt@novell.com>", /**< author */
5547 PURPLE_WEBSITE, /**< homepage */
5548 sipe_plugin_load, /**< load */
5549 sipe_plugin_unload, /**< unload */
5550 sipe_plugin_destroy, /**< destroy */
5551 NULL, /**< ui_info */
5552 &prpl_info, /**< extra_info */
5553 NULL,
5554 sipe_actions,
5555 NULL,
5556 NULL,
5557 NULL,
5558 NULL
5561 static void sipe_plugin_destroy(PurplePlugin *plugin)
5563 GList *entry;
5565 entry = prpl_info.protocol_options;
5566 while (entry) {
5567 purple_account_option_destroy(entry->data);
5568 entry = g_list_delete_link(entry, entry);
5570 prpl_info.protocol_options = NULL;
5572 entry = prpl_info.user_splits;
5573 while (entry) {
5574 purple_account_user_split_destroy(entry->data);
5575 entry = g_list_delete_link(entry, entry);
5577 prpl_info.user_splits = NULL;
5580 static void init_plugin(PurplePlugin *plugin)
5582 PurpleAccountUserSplit *split;
5583 PurpleAccountOption *option;
5585 #ifdef ENABLE_NLS
5586 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5587 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5588 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5589 #endif
5591 purple_plugin_register(plugin);
5593 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5594 purple_account_user_split_set_reverse(split, FALSE);
5595 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5597 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5598 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5599 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5600 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5602 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5603 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5604 // Translators: noun (networking port)
5605 option = purple_account_option_int_new(_("Port"), "port", 5061);
5606 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5608 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5609 purple_account_option_add_list_item(option, _("Auto"), "auto");
5610 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5611 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5612 purple_account_option_add_list_item(option, _("UDP"), "udp");
5613 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5615 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5616 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5618 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5619 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5621 // TODO commented out so won't show in the preferences until we fix krb message signing
5622 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5623 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5625 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5626 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5627 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5630 /*option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5631 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5632 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5633 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5634 my_protocol = plugin;
5637 /* I had to redefined the function for it load, but works */
5638 gboolean purple_init_plugin(PurplePlugin *plugin){
5639 plugin->info = &(info);
5640 init_plugin((plugin));
5641 sipe_plugin_load((plugin));
5642 return purple_plugin_register(plugin);
5646 Local Variables:
5647 mode: c
5648 c-file-style: "bsd"
5649 indent-tabs-mode: t
5650 tab-width: 8
5651 End: