Full support for Batched Category Subscription for OCS2007.
[siplcs.git] / src / sipe.c
blobc39b9a99a86630b16ee580b9a1341c105e9fe15c
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2007 Anibal Avelar <avelar@gmail.com>
8 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
10 * ***
11 * Thanks to Google's Summer of Code Program and the helpful mentors
12 * ***
14 * Session-based SIP MESSAGE documentation:
15 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 #ifndef _WIN32
33 #include <sys/socket.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
36 #include <netinet/in.h>
37 #include <net/if.h>
38 #ifdef ENABLE_NLS
39 # include <libintl.h>
40 # define _(String) ((const char *) gettext (String))
41 #else
42 # define _(String) ((const char *) (String))
43 #endif /* ENABLE_NLS */
44 #else
45 #ifdef _DLL
46 #define _WS2TCPIP_H_
47 #define _WINSOCK2API_
48 #define _LIBC_INTERNAL_
49 #endif /* _DLL */
51 #include "internal.h"
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <glib.h>
61 #include "accountopt.h"
62 #include "blist.h"
63 #include "conversation.h"
64 #include "dnsquery.h"
65 #include "debug.h"
66 #include "notify.h"
67 #include "privacy.h"
68 #include "prpl.h"
69 #include "plugin.h"
70 #include "util.h"
71 #include "version.h"
72 #include "network.h"
73 #include "xmlnode.h"
74 #include "mime.h"
76 #include "sipe.h"
77 #include "sip-ntlm.h"
78 #ifdef USE_KERBEROS
79 #include "sipkrb5.h"
80 #endif /*USE_KERBEROS*/
82 #include "sipmsg.h"
83 #include "sipe-sign.h"
84 #include "dnssrv.h"
85 #include "request.h"
87 /* Keep in sync with sipe_transport_type! */
88 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
89 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
91 static char *gentag()
93 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
96 static gchar *get_epid()
98 return sipe_uuid_get_macaddr();
101 static char *genbranch()
103 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
104 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
105 rand() & 0xFFFF, rand() & 0xFFFF);
108 static char *gencallid()
110 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
111 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
112 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
113 rand() & 0xFFFF, rand() & 0xFFFF);
116 static gchar *find_tag(const gchar *hdr)
118 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
119 if (!tag) {
120 // In case it's at the end and there's no trailing ;
121 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
123 return tag;
127 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
129 return "sipe";
132 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
134 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
135 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
136 gpointer data);
138 static void sipe_close(PurpleConnection *gc);
140 static void sipe_subscribe_to_name_single(struct sipe_account_data *sip, const char * buddy_name);
141 static void sipe_subscribe_to_buddies_batched(struct sipe_account_data *sip);
142 static void send_presence_info(struct sipe_account_data *sip);
144 static void sendout_pkt(PurpleConnection *gc, const char *buf);
146 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
148 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
149 if (timeout != NULL) {
150 sscanf(timeout, "%u", &sip->keepalive_timeout);
151 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
152 sip->keepalive_timeout);
156 static void sipe_keep_alive(PurpleConnection *gc)
158 struct sipe_account_data *sip = gc->proto_data;
159 if (sip->transport == SIPE_TRANSPORT_UDP) {
160 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
161 gchar buf[2] = {0, 0};
162 purple_debug_info("sipe", "sending keep alive\n");
163 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
164 } else {
165 time_t now = time(NULL);
166 if ((sip->keepalive_timeout > 0) &&
167 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
168 #if PURPLE_VERSION_CHECK(2,4,0)
169 && ((now - gc->last_received) >= sip->keepalive_timeout)
170 #endif
172 purple_debug_info("sipe", "sending keep alive\n");
173 sendout_pkt(gc, "\r\n\r\n");
174 sip->last_keepalive = now;
179 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
181 struct sip_connection *ret = NULL;
182 GSList *entry = sip->openconns;
183 while (entry) {
184 ret = entry->data;
185 if (ret->fd == fd) return ret;
186 entry = entry->next;
188 return NULL;
191 static void sipe_auth_free(struct sip_auth *auth)
193 g_free(auth->nonce);
194 auth->nonce = NULL;
195 g_free(auth->opaque);
196 auth->opaque = NULL;
197 g_free(auth->realm);
198 auth->realm = NULL;
199 g_free(auth->target);
200 auth->target = NULL;
201 g_free(auth->digest_session_key);
202 auth->digest_session_key = NULL;
203 g_free(auth->ntlm_key);
204 auth->ntlm_key = NULL;
205 auth->type = AUTH_TYPE_UNSET;
206 auth->retries = 0;
207 auth->expires = 0;
210 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
212 struct sip_connection *ret = g_new0(struct sip_connection, 1);
213 ret->fd = fd;
214 sip->openconns = g_slist_append(sip->openconns, ret);
215 return ret;
218 static void connection_remove(struct sipe_account_data *sip, int fd)
220 struct sip_connection *conn = connection_find(sip, fd);
221 if (conn) {
222 sip->openconns = g_slist_remove(sip->openconns, conn);
223 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
224 g_free(conn->inbuf);
225 g_free(conn);
229 static void connection_free_all(struct sipe_account_data *sip)
231 struct sip_connection *ret = NULL;
232 GSList *entry = sip->openconns;
233 while (entry) {
234 ret = entry->data;
235 connection_remove(sip, ret->fd);
236 entry = sip->openconns;
240 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
242 const gchar *method = msg->method;
243 const gchar *target = msg->target;
244 gchar noncecount[9];
245 gchar *response;
246 gchar *ret;
247 gchar *tmp = NULL;
248 const char *authdomain = sip->authdomain;
249 const char *authuser = sip->authuser;
250 //const char *krb5_realm;
251 const char *host;
252 //gchar *krb5_token = NULL;
254 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
255 // and do error checking
257 // KRB realm should always be uppercase
258 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
260 if (sip->realhostname) {
261 host = sip->realhostname;
262 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
263 host = purple_account_get_string(sip->account, "proxy", "");
264 } else {
265 host = sip->sipdomain;
268 /*gboolean new_auth = krb5_auth.gss_context == NULL;
269 if (new_auth) {
270 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
273 if (new_auth || force_reauth) {
274 krb5_token = krb5_auth.base64_token;
277 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
278 krb5_token = krb5_auth.base64_token;*/
280 if (!authdomain) {
281 authdomain = "";
284 if (!authuser || strlen(authuser) < 1) {
285 authuser = sip->username;
288 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
289 sprintf(noncecount, "%08d", auth->nc++);
290 response = purple_cipher_http_digest_calculate_response(
291 "md5", method, target, NULL, NULL,
292 auth->nonce, noncecount, NULL, auth->digest_session_key);
293 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
295 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);
296 g_free(response);
297 return ret;
298 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
299 // If we have a signature for the message, include that
300 if (msg->signature) {
301 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);
302 return tmp;
305 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
306 const gchar *ntlm_key;
307 gchar *gssapi_data;
308 #if GLIB_CHECK_VERSION(2,8,0)
309 const gchar * hostname = g_get_host_name();
310 #else
311 static char hostname[256];
312 int ret = gethostname(hostname, sizeof(hostname));
313 hostname[sizeof(hostname) - 1] = '\0';
314 if (ret == -1 || hostname[0] == '\0') {
315 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
316 g_strerror(errno);
317 strcpy(hostname, "localhost");
319 #endif
320 /*const gchar * hostname = purple_get_host_name();*/
322 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
323 auth->ntlm_key = (gchar *)ntlm_key;
324 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
325 g_free(gssapi_data);
326 return tmp;
329 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
330 return tmp;
331 } else if (auth->type == AUTH_TYPE_KERBEROS) {
332 /* Kerberos */
333 if (auth->nc == 3) {
334 /*if (new_auth || force_reauth) {
335 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
336 if (auth->opaque) {
337 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);
338 } else {
339 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
341 } else {
342 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
343 gchar * mic = "MICTODO";
344 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
345 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
346 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
347 //auth->opaque ? auth->opaque : "", auth->target);
348 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
349 //g_free(mic);
351 return tmp;
353 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
356 sprintf(noncecount, "%08d", auth->nc++);
357 response = purple_cipher_http_digest_calculate_response(
358 "md5", method, target, NULL, NULL,
359 auth->nonce, noncecount, NULL, auth->digest_session_key);
360 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
362 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);
363 g_free(response);
364 return ret;
367 static char *parse_attribute(const char *attrname, const char *source)
369 const char *tmp, *tmp2;
370 char *retval = NULL;
371 int len = strlen(attrname);
373 if (!strncmp(source, attrname, len)) {
374 tmp = source + len;
375 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
376 if (tmp2)
377 retval = g_strndup(tmp, tmp2 - tmp);
378 else
379 retval = g_strdup(tmp);
382 return retval;
385 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
387 int i = 0;
388 const char *authuser;
389 char *tmp;
390 gchar **parts;
391 //const char *krb5_realm;
392 //const char *host;
394 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
395 // and do error checking
397 // KRB realm should always be uppercase
398 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
400 if (sip->realhostname) {
401 host = sip->realhostname;
402 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
403 host = purple_account_get_string(sip->account, "proxy", "");
404 } else {
405 host = sip->sipdomain;
408 authuser = sip->authuser;
410 if (!authuser || strlen(authuser) < 1) {
411 authuser = sip->username;
414 if (!hdr) {
415 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
416 return;
419 if (!g_strncasecmp(hdr, "NTLM", 4)) {
420 auth->type = AUTH_TYPE_NTLM;
421 parts = g_strsplit(hdr+5, "\", ", 0);
422 i = 0;
423 while (parts[i]) {
424 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
425 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
426 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
427 g_free(tmp);
429 if ((tmp = parse_attribute("targetname=\"",
430 parts[i]))) {
431 auth->target = tmp;
433 else if ((tmp = parse_attribute("realm=\"",
434 parts[i]))) {
435 auth->realm = tmp;
437 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
438 auth->opaque = tmp;
440 i++;
442 g_strfreev(parts);
443 auth->nc = 1;
444 if (!strstr(hdr, "gssapi-data")) {
445 auth->nc = 1;
446 } else {
447 auth->nc = 3;
449 return;
452 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
453 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
454 auth->type = AUTH_TYPE_KERBEROS;
455 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
456 parts = g_strsplit(hdr+9, "\", ", 0);
457 i = 0;
458 while (parts[i]) {
459 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
460 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
461 /*if (krb5_auth.gss_context == NULL) {
462 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
464 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
465 g_free(tmp);
467 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
468 auth->target = tmp;
469 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
470 auth->realm = tmp;
471 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
472 auth->opaque = tmp;
474 i++;
476 g_strfreev(parts);
477 auth->nc = 3;
478 return;
481 auth->type = AUTH_TYPE_DIGEST;
482 parts = g_strsplit(hdr, " ", 0);
483 while (parts[i]) {
484 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
485 auth->nonce = tmp;
487 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
488 auth->realm = tmp;
490 i++;
492 g_strfreev(parts);
494 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
495 if (auth->realm) {
496 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
497 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
499 auth->nc = 1;
503 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
505 PurpleConnection *gc = data;
506 struct sipe_account_data *sip = gc->proto_data;
507 gsize max_write;
508 gssize written;
510 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
512 if (max_write == 0) {
513 if (sip->tx_handler != 0){
514 purple_input_remove(sip->tx_handler);
515 sip->tx_handler = 0;
517 return;
520 written = write(sip->fd, sip->txbuf->outptr, max_write);
522 if (written < 0 && errno == EAGAIN)
523 written = 0;
524 else if (written <= 0) {
525 /*TODO: do we really want to disconnect on a failure to write?*/
526 purple_connection_error(gc, _("Could not write"));
527 return;
530 purple_circ_buffer_mark_read(sip->txbuf, written);
533 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
535 PurpleConnection *gc = data;
536 struct sipe_account_data *sip = gc->proto_data;
537 gsize max_write;
538 gssize written;
540 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
542 if (max_write == 0) {
543 if (sip->tx_handler != 0) {
544 purple_input_remove(sip->tx_handler);
545 sip->tx_handler = 0;
546 return;
550 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
552 if (written < 0 && errno == EAGAIN)
553 written = 0;
554 else if (written <= 0) {
555 /*TODO: do we really want to disconnect on a failure to write?*/
556 purple_connection_error(gc, _("Could not write"));
557 return;
560 purple_circ_buffer_mark_read(sip->txbuf, written);
563 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
565 static void send_later_cb(gpointer data, gint source, const gchar *error)
567 PurpleConnection *gc = data;
568 struct sipe_account_data *sip;
569 struct sip_connection *conn;
571 if (!PURPLE_CONNECTION_IS_VALID(gc))
573 if (source >= 0)
574 close(source);
575 return;
578 if (source < 0) {
579 purple_connection_error(gc, _("Could not connect"));
580 return;
583 sip = gc->proto_data;
584 sip->fd = source;
585 sip->connecting = FALSE;
586 sip->last_keepalive = time(NULL);
588 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
590 /* If there is more to write now, we need to register a handler */
591 if (sip->txbuf->bufused > 0)
592 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
594 conn = connection_create(sip, source);
595 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
598 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
600 struct sipe_account_data *sip;
601 struct sip_connection *conn;
603 if (!PURPLE_CONNECTION_IS_VALID(gc))
605 if (gsc) purple_ssl_close(gsc);
606 return NULL;
609 sip = gc->proto_data;
610 sip->fd = gsc->fd;
611 sip->gsc = gsc;
612 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
613 sip->connecting = FALSE;
614 sip->last_keepalive = time(NULL);
616 conn = connection_create(sip, gsc->fd);
618 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
620 return sip;
623 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
625 PurpleConnection *gc = data;
626 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
627 if (sip == NULL) return;
629 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
631 /* If there is more to write now */
632 if (sip->txbuf->bufused > 0) {
633 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
638 static void sendlater(PurpleConnection *gc, const char *buf)
640 struct sipe_account_data *sip = gc->proto_data;
642 if (!sip->connecting) {
643 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
644 if (sip->transport == SIPE_TRANSPORT_TLS){
645 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
646 } else {
647 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
648 purple_connection_error(gc, _("Couldn't create socket"));
651 sip->connecting = TRUE;
654 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
655 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
657 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
660 static void sendout_pkt(PurpleConnection *gc, const char *buf)
662 struct sipe_account_data *sip = gc->proto_data;
663 time_t currtime = time(NULL);
664 int writelen = strlen(buf);
666 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
667 if (sip->transport == SIPE_TRANSPORT_UDP) {
668 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
669 purple_debug_info("sipe", "could not send packet\n");
671 } else {
672 int ret;
673 if (sip->fd < 0) {
674 sendlater(gc, buf);
675 return;
678 if (sip->tx_handler) {
679 ret = -1;
680 errno = EAGAIN;
681 } else{
682 if (sip->gsc){
683 ret = purple_ssl_write(sip->gsc, buf, writelen);
684 }else{
685 ret = write(sip->fd, buf, writelen);
689 if (ret < 0 && errno == EAGAIN)
690 ret = 0;
691 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
692 sendlater(gc, buf);
693 return;
696 if (ret < writelen) {
697 if (!sip->tx_handler){
698 if (sip->gsc){
699 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
701 else{
702 sip->tx_handler = purple_input_add(sip->fd,
703 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
704 gc);
708 /* XXX: is it OK to do this? You might get part of a request sent
709 with part of another. */
710 if (sip->txbuf->bufused > 0)
711 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
713 purple_circ_buffer_append(sip->txbuf, buf + ret,
714 writelen - ret);
719 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
721 sendout_pkt(gc, buf);
722 return len;
725 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
727 GSList *tmp = msg->headers;
728 gchar *name;
729 gchar *value;
730 GString *outstr = g_string_new("");
731 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
732 while (tmp) {
733 name = ((struct siphdrelement*) (tmp->data))->name;
734 value = ((struct siphdrelement*) (tmp->data))->value;
735 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
736 tmp = g_slist_next(tmp);
738 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
739 sendout_pkt(sip->gc, outstr->str);
740 g_string_free(outstr, TRUE);
743 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
745 gchar * buf;
746 if (sip->registrar.ntlm_key) {
747 struct sipmsg_breakdown msgbd;
748 gchar *signature_input_str;
749 msgbd.msg = msg;
750 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
751 msgbd.rand = g_strdup_printf("%08x", g_random_int());
752 sip->registrar.ntlm_num++;
753 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
754 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
755 if (signature_input_str != NULL) {
756 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
757 msg->rand = g_strdup(msgbd.rand);
758 msg->num = g_strdup(msgbd.num);
759 g_free(signature_input_str);
761 sipmsg_breakdown_free(&msgbd);
764 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
765 buf = auth_header(sip, &sip->registrar, msg);
766 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
767 sipmsg_add_header(msg, "Authorization", buf);
768 } else {
769 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
771 g_free(buf);
772 } 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")) {
773 sip->registrar.nc = 3;
774 sip->registrar.type = AUTH_TYPE_NTLM;
776 buf = auth_header(sip, &sip->registrar, msg);
777 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
778 g_free(buf);
779 } else {
780 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
784 static char *get_contact(struct sipe_account_data *sip)
786 return g_strdup(sip->contact);
790 * unused. Needed?
791 static char *get_contact_service(struct sipe_account_data *sip)
793 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()));
794 //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);
798 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
799 const char *text, const char *body)
801 gchar *name;
802 gchar *value;
803 GString *outstr = g_string_new("");
804 struct sipe_account_data *sip = gc->proto_data;
805 gchar *contact;
806 GSList *tmp;
808 sipmsg_remove_header(msg, "ms-user-data");
810 contact = get_contact(sip);
811 sipmsg_remove_header(msg, "Contact");
812 sipmsg_add_header(msg, "Contact", contact);
813 g_free(contact);
815 /* When sending the acknowlegements and errors, the content length from the original
816 message is still here, but there is no body; we need to make sure we're sending the
817 correct content length */
818 sipmsg_remove_header(msg, "Content-Length");
819 if (body) {
820 gchar len[12];
821 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
822 sipmsg_add_header(msg, "Content-Length", len);
823 } else {
824 sipmsg_remove_header(msg, "Content-Type");
825 sipmsg_add_header(msg, "Content-Length", "0");
828 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
829 //gchar * mic = "MICTODO";
830 msg->response = code;
832 sipmsg_remove_header(msg, "Authentication-Info");
833 sign_outgoing_message(msg, sip, msg->method);
835 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
836 tmp = msg->headers;
837 while (tmp) {
838 name = ((struct siphdrelement*) (tmp->data))->name;
839 value = ((struct siphdrelement*) (tmp->data))->value;
841 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
842 tmp = g_slist_next(tmp);
844 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
845 sendout_pkt(gc, outstr->str);
846 g_string_free(outstr, TRUE);
849 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
851 if (trans->msg) sipmsg_free(trans->msg);
852 sip->transactions = g_slist_remove(sip->transactions, trans);
853 g_free(trans);
856 static struct transaction *
857 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
859 struct transaction *trans = g_new0(struct transaction, 1);
860 trans->time = time(NULL);
861 trans->msg = (struct sipmsg *)msg;
862 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
863 trans->callback = callback;
864 sip->transactions = g_slist_append(sip->transactions, trans);
865 return trans;
868 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
870 struct transaction *trans;
871 GSList *transactions = sip->transactions;
872 gchar *cseq = sipmsg_find_header(msg, "CSeq");
874 while (transactions) {
875 trans = transactions->data;
876 if (!strcmp(trans->cseq, cseq)) {
877 return trans;
879 transactions = transactions->next;
882 return NULL;
885 static struct transaction *
886 send_sip_request(PurpleConnection *gc, const gchar *method,
887 const gchar *url, const gchar *to, const gchar *addheaders,
888 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
890 struct sipe_account_data *sip = gc->proto_data;
891 const char *addh = "";
892 char *buf;
893 struct sipmsg *msg;
894 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
895 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
896 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
897 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
898 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
899 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
900 gchar *route = strdup("");
901 gchar *epid = get_epid(); // TODO generate one per account/login
902 struct transaction *trans;
904 if (dialog && dialog->routes)
906 GSList *iter = dialog->routes;
908 while(iter)
910 char *tmp = route;
911 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
912 g_free(tmp);
913 iter = g_slist_next(iter);
917 if (!ourtag && !dialog) {
918 ourtag = gentag();
921 if (!strcmp(method, "REGISTER")) {
922 if (sip->regcallid) {
923 g_free(callid);
924 callid = g_strdup(sip->regcallid);
925 } else {
926 sip->regcallid = g_strdup(callid);
930 if (addheaders) addh = addheaders;
932 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
933 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
934 "From: <sip:%s>%s%s;epid=%s\r\n"
935 "To: <%s>%s%s%s%s\r\n"
936 "Max-Forwards: 70\r\n"
937 "CSeq: %d %s\r\n"
938 "User-Agent: %s\r\n"
939 "Call-ID: %s\r\n"
940 "%s%s"
941 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
942 method,
943 dialog && dialog->request ? dialog->request : url,
944 TRANSPORT_DESCRIPTOR,
945 purple_network_get_my_ip(-1),
946 sip->listenport,
947 branch ? ";branch=" : "",
948 branch ? branch : "",
949 sip->username,
950 ourtag ? ";tag=" : "",
951 ourtag ? ourtag : "",
952 epid,
954 theirtag ? ";tag=" : "",
955 theirtag ? theirtag : "",
956 theirepid ? ";epid=" : "",
957 theirepid ? theirepid : "",
958 dialog ? ++dialog->cseq : ++sip->cseq,
959 method,
960 useragent,
961 callid,
962 route,
963 addh,
964 body ? strlen(body) : 0,
965 body ? body : "");
968 //printf ("parsing msg buf:\n%s\n\n", buf);
969 msg = sipmsg_parse_msg(buf);
971 g_free(buf);
972 g_free(ourtag);
973 g_free(theirtag);
974 g_free(theirepid);
975 g_free(branch);
976 g_free(callid);
977 g_free(route);
978 g_free(epid);
980 sign_outgoing_message (msg, sip, method);
982 buf = sipmsg_to_string (msg);
984 /* add to ongoing transactions */
985 trans = transactions_add_buf(sip, msg, tc);
986 sendout_pkt(gc, buf);
987 g_free(buf);
989 return trans;
992 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
994 gchar *from = g_strdup_printf("sip:%s", sip->username);
995 gchar *contact = get_contact(sip);
996 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
997 "Content-Type: application/SOAP+xml\r\n",contact);
999 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1000 tr->payload = payload;
1002 g_free(from);
1003 g_free(hdr);
1006 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1008 send_soap_request_with_cb(sip, body, NULL, NULL);
1011 static char *get_contact_register(struct sipe_account_data *sip)
1013 char *epid = get_epid();
1014 char *uuid = generateUUIDfromEPID(epid);
1015 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1016 g_free(uuid);
1017 g_free(epid);
1018 return(buf);
1021 static void do_register_exp(struct sipe_account_data *sip, int expire)
1023 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1024 char *to = g_strdup_printf("sip:%s", sip->username);
1025 char *contact = get_contact_register(sip);
1026 char *hdr = g_strdup_printf("Contact: %s\r\n"
1027 "Supported: gruu-10, adhoclist, msrtc-event-categories\r\n"
1028 "Event: registration\r\n"
1029 "Allow-Events: presence\r\n"
1030 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1031 "Expires: %d\r\n", contact,expire);
1032 g_free(contact);
1034 sip->registerstatus = 1;
1036 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1037 process_register_response);
1039 g_free(hdr);
1040 g_free(uri);
1041 g_free(to);
1044 static void do_register_cb(struct sipe_account_data *sip)
1046 do_register_exp(sip, sip->registerexpire);
1047 sip->reregister_set = FALSE;
1050 static void do_register(struct sipe_account_data *sip)
1052 do_register_exp(sip, sip->registerexpire);
1056 * Returns URI from provided To or From header.
1058 * Needs to g_free() after use.
1060 * @return URI with sip: prefix
1062 static gchar *parse_from(const gchar *hdr)
1064 gchar *from;
1065 const gchar *tmp, *tmp2 = hdr;
1067 if (!hdr) return NULL;
1068 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1069 tmp = strchr(hdr, '<');
1071 /* i hate the different SIP UA behaviours... */
1072 if (tmp) { /* sip address in <...> */
1073 tmp2 = tmp + 1;
1074 tmp = strchr(tmp2, '>');
1075 if (tmp) {
1076 from = g_strndup(tmp2, tmp - tmp2);
1077 } else {
1078 purple_debug_info("sipe", "found < without > in From\n");
1079 return NULL;
1081 } else {
1082 tmp = strchr(tmp2, ';');
1083 if (tmp) {
1084 from = g_strndup(tmp2, tmp - tmp2);
1085 } else {
1086 from = g_strdup(tmp2);
1089 purple_debug_info("sipe", "got %s\n", from);
1090 return from;
1093 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1095 va_list args;
1096 xmlnode * node = NULL;
1097 const gchar * name;
1099 va_start(args, parent);
1100 while ((name = va_arg(args, const char *)) != NULL) {
1101 node = xmlnode_get_child(parent, name);
1102 if (node == NULL) return NULL;
1103 parent = node;
1105 va_end(args);
1107 return node;
1111 static void
1112 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1114 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1115 send_soap_request(sip, body);
1116 g_free(body);
1119 static void
1120 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1122 if (allow) {
1123 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1124 } else {
1125 purple_debug_info("sipe", "Blocking contact %s\n", who);
1128 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1131 static
1132 void sipe_auth_user_cb(void * data)
1134 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1135 if (!job) return;
1137 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1138 g_free(job);
1141 static
1142 void sipe_deny_user_cb(void * data)
1144 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1145 if (!job) return;
1147 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1148 g_free(job);
1151 static void
1152 sipe_add_permit(PurpleConnection *gc, const char *name)
1154 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1155 sipe_contact_allow_deny(sip, name, TRUE);
1158 static void
1159 sipe_add_deny(PurpleConnection *gc, const char *name)
1161 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1162 sipe_contact_allow_deny(sip, name, FALSE);
1165 /*static void
1166 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1168 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1169 sipe_contact_set_acl(sip, name, "");
1172 static void
1173 sipe_process_incoming_pending (struct sipe_account_data *sip, struct sipmsg * msg)
1175 xmlnode *watchers;
1176 xmlnode *watcher;
1177 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1178 if (msg->response != 0 && msg->response != 200) return;
1180 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1182 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1183 if (!watchers) return;
1185 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1186 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1187 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1188 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1190 // TODO pull out optional displayName to pass as alias
1191 if (remote_user) {
1192 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1193 job->who = remote_user;
1194 job->sip = sip;
1195 purple_account_request_authorization(
1196 sip->account,
1197 remote_user,
1198 NULL, // id
1199 alias,
1200 NULL, // message
1201 on_list,
1202 sipe_auth_user_cb,
1203 sipe_deny_user_cb,
1204 (void *) job);
1209 xmlnode_free(watchers);
1210 return;
1213 static void
1214 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1216 PurpleGroup * purple_group = purple_find_group(group->name);
1217 if (!purple_group) {
1218 purple_group = purple_group_new(group->name);
1219 purple_blist_add_group(purple_group, NULL);
1222 if (purple_group) {
1223 group->purple_group = purple_group;
1224 sip->groups = g_slist_append(sip->groups, group);
1225 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1226 } else {
1227 purple_debug_info("sipe", "did not add group %s\n", group->name);
1231 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1233 struct sipe_group *group;
1234 GSList *entry;
1235 if (sip == NULL) {
1236 return NULL;
1239 entry = sip->groups;
1240 while (entry) {
1241 group = entry->data;
1242 if (group->id == id) {
1243 return group;
1245 entry = entry->next;
1247 return NULL;
1250 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1252 struct sipe_group *group;
1253 GSList *entry;
1254 if (sip == NULL) {
1255 return NULL;
1258 entry = sip->groups;
1259 while (entry) {
1260 group = entry->data;
1261 if (!strcmp(group->name, name)) {
1262 return group;
1264 entry = entry->next;
1266 return NULL;
1269 static void
1270 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1272 gchar *body;
1273 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1274 body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1275 send_soap_request(sip, body);
1276 g_free(body);
1277 g_free(group->name);
1278 group->name = g_strdup(name);
1282 * Only appends if no such value already stored.
1283 * Like Set in Java.
1285 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1286 GSList * res = list;
1287 if (!g_slist_find_custom(list, data, func)) {
1288 res = g_slist_insert_sorted(list, data, func);
1290 return res;
1293 static int
1294 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1295 return group1->id - group2->id;
1299 * Returns string like "2 4 7 8" - group ids buddy belong to.
1301 static gchar *
1302 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1303 int i = 0;
1304 gchar *res;
1305 //creating array from GList, converting int to gchar*
1306 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1307 GSList *entry = buddy->groups;
1308 while (entry) {
1309 struct sipe_group * group = entry->data;
1310 ids_arr[i] = g_strdup_printf("%d", group->id);
1311 entry = entry->next;
1312 i++;
1314 ids_arr[i] = NULL;
1315 res = g_strjoinv(" ", ids_arr);
1316 g_strfreev(ids_arr);
1317 return res;
1321 * Sends buddy update to server
1323 static void
1324 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1326 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1327 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1329 if (buddy && purple_buddy) {
1330 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1331 gchar *body;
1332 gchar *groups = sipe_get_buddy_groups_string(buddy);
1333 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1335 body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1336 alias, groups, "true", buddy->name, sip->contacts_delta++
1338 send_soap_request(sip, body);
1339 g_free(groups);
1340 g_free(body);
1344 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1346 if (msg->response == 200) {
1347 struct sipe_group * group = g_new0(struct sipe_group, 1);
1349 struct group_user_context * ctx = (struct group_user_context*)tc->payload;
1350 xmlnode *xml;
1351 xmlnode *node;
1352 char *group_id;
1353 struct sipe_buddy *buddy;
1354 group->name = ctx->group_name;
1356 xml = xmlnode_from_str(msg->body, msg->bodylen);
1357 if (!xml) return FALSE;
1359 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1360 if (!node) return FALSE;
1362 group_id = xmlnode_get_data(node);
1363 if (!group_id) return FALSE;
1365 group->id = (int)g_ascii_strtod(group_id, NULL);
1367 sipe_group_add(sip, group);
1369 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1370 if (buddy) {
1371 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1374 sipe_group_set_user(sip, ctx->user_name);
1376 g_free(ctx);
1377 xmlnode_free(xml);
1378 return TRUE;
1380 return FALSE;
1383 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1385 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1386 gchar *body;
1387 ctx->group_name = g_strdup(name);
1388 ctx->user_name = g_strdup(who);
1390 body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1391 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1392 g_free(body);
1396 * A timer callback
1397 * Should return FALSE if repetitive action is not needed
1399 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1401 gboolean ret;
1402 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1403 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1404 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1405 (sched_action->action)(sched_action->sip, sched_action->payload);
1406 ret = sched_action->repetitive;
1407 g_free(sched_action->payload);
1408 g_free(sched_action->name);
1409 g_free(sched_action);
1410 return ret;
1414 * Do schedule action for execution in the future.
1415 * Non repetitive execution.
1417 * @param name of action (will be copied)
1418 * @param timeout in seconds
1419 * @action callback function
1420 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1422 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1424 struct scheduled_action *sched_action;
1426 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1427 sched_action = g_new0(struct scheduled_action, 1);
1428 sched_action->repetitive = FALSE;
1429 sched_action->name = g_strdup(name);
1430 sched_action->action = action;
1431 sched_action->sip = sip;
1432 sched_action->payload = payload;
1433 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1434 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1435 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1439 * Kills action timer effectively cancelling
1440 * scheduled action
1442 * @param name of action
1444 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1446 GSList *entry;
1448 if (!sip->timeouts || !name) return;
1450 entry = sip->timeouts;
1451 while (entry) {
1452 struct scheduled_action *sched_action = entry->data;
1453 if(!strcmp(sched_action->name, name)) {
1454 GSList *to_delete = entry;
1455 entry = entry->next;
1456 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1457 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1458 purple_timeout_remove(sched_action->timeout_handler);
1459 g_free(sched_action->payload);
1460 g_free(sched_action->name);
1461 g_free(sched_action);
1462 } else {
1463 entry = entry->next;
1468 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1470 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1472 gchar *to;
1473 //purple_debug_info("sipe","process_subscribe_response: body:\n%s\n", msg->body);
1475 if (msg->response == 200 || msg->response == 202)
1477 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1479 process_incoming_notify(sip, msg, FALSE, FALSE);
1481 return TRUE;
1484 /* we can not subscribe -> user is offline (TODO unknown status?) */
1485 to = parse_from(sipmsg_find_header(tc->msg, "To")); /* can't be NULL since it is our own msg */
1486 purple_prpl_got_user_status(sip->account, to, "offline", NULL);
1487 g_free(to);
1488 return TRUE;
1493 * Batch Category SUBSCRIBE [SIP-PRES] - msrtc-event-categories+xml
1494 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1495 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1496 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1500 static void sipe_subscribe_to_buddies_batched(struct sipe_account_data *sip){
1501 GList *entry = g_hash_table_get_values (sip->buddies);
1502 struct sipe_buddy * buddy;
1503 gchar *to = g_strdup_printf("sip:%s", sip->username);
1504 gchar *tmp = get_contact(sip);
1505 gchar *request;
1506 gchar *content;
1507 gchar *resources_uri = g_strdup("<adhocList>\n");
1509 purple_debug_info("sipe", " sipe_subscribe_to_buddies_batched buddies size (%d)\n", g_hash_table_size(sip->buddies));
1510 while (entry) {
1511 buddy = entry->data;
1512 purple_debug_info("sipe", "sipe_subscribe_to_buddies_batched (%s)\n", buddy->name);
1513 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", resources_uri, buddy->name);
1514 entry = entry->next;
1517 resources_uri = g_strdup_printf("%s</adhocList>\n",resources_uri);
1519 request = g_strdup_printf(
1520 "Require: adhoclist, categoryList\r\n"
1521 "Supported: eventlist\r\n"
1522 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1523 "Supported: ms-piggyback-first-notify\r\n"
1524 "Supported: com.microsoft.autoextend\r\n"
1525 "Supported: ms-benotify\r\n"
1526 "Proxy-Require: ms-benotify\r\n"
1527 "Event: presence\r\n"
1528 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1529 "Contact: %s\r\n", tmp);
1531 content = g_strdup_printf(
1532 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1533 "<action name=\"subscribe\" id=\"63792024\">\n"
1534 "%s"
1535 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1536 "<category name=\"note\"/>\n"
1537 "<category name=\"state\"/>\n"
1538 "</categoryList>\n"
1539 "</action>\n"
1540 "</batchSub>", sip->username, resources_uri
1543 g_free(tmp);
1545 /* subscribe to buddy presence */
1546 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1547 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1549 g_free(content);
1550 g_free(to);
1551 g_free(resources_uri);
1552 g_free(request);
1556 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1557 * The user sends a single SUBSCRIBE request to the subscribed contact.
1558 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1562 static void sipe_subscribe_to_name_single(struct sipe_account_data *sip, const char * buddy_name)
1564 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1565 gchar *tmp = get_contact(sip);
1566 gchar *request;
1567 gchar *content;
1568 request = g_strdup_printf(
1569 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1570 "Supported: ms-piggyback-first-notify\r\n"
1571 "Supported: com.microsoft.autoextend\r\n"
1572 "Supported: ms-benotify\r\n"
1573 "Proxy-Require: ms-benotify\r\n"
1574 "Event: presence\r\n"
1575 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1576 "Contact: %s\r\n", tmp);
1578 content = g_strdup_printf(
1579 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1580 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1581 "<resource uri=\"%s\"/>\n"
1582 "</adhocList>\n"
1583 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1584 "<category name=\"note\"/>\n"
1585 "<category name=\"state\"/>\n"
1586 "</categoryList>\n"
1587 "</action>\n"
1588 "</batchSub>", sip->username, to
1591 g_free(tmp);
1593 /* subscribe to buddy presence */
1594 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1596 g_free(content);
1597 g_free(to);
1598 g_free(request);
1601 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1603 const char *status_id = purple_status_get_id(status);
1604 struct sipe_account_data *sip = NULL;
1606 if (!purple_status_is_active(status))
1607 return;
1609 if (account->gc)
1610 sip = account->gc->proto_data;
1612 if (sip) {
1613 g_free(sip->status);
1614 sip->status = g_strdup(status_id);
1615 send_presence_info(sip);
1619 static void
1620 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1622 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1623 sipe_group_set_user(sip, name);
1626 static void
1627 sipe_group_buddy(PurpleConnection *gc,
1628 const char *who,
1629 const char *old_group_name,
1630 const char *new_group_name)
1632 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1633 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1634 struct sipe_group * old_group = NULL;
1635 struct sipe_group * new_group;
1637 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1638 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1640 if(!buddy) { // buddy not in roaming list
1641 return;
1644 if (old_group_name) {
1645 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1647 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1649 if (old_group) {
1650 buddy->groups = g_slist_remove(buddy->groups, old_group);
1651 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1654 if (!new_group) {
1655 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1656 } else {
1657 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1658 sipe_group_set_user(sip, who);
1662 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1664 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1665 struct sipe_buddy *b;
1667 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1669 // Prepend sip: if needed
1670 if (strncmp("sip:", buddy->name, 4)) {
1671 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1672 purple_blist_rename_buddy(buddy, buf);
1673 g_free(buf);
1676 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1677 b = g_new0(struct sipe_buddy, 1);
1678 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1679 b->name = g_strdup(buddy->name);
1680 g_hash_table_insert(sip->buddies, b->name, b);
1681 sipe_group_buddy(gc, b->name, NULL, group->name);
1682 sipe_subscribe_to_name_single(sip, b->name); //@TODO should go to callback
1683 } else {
1684 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1689 * Unassociates buddy from group first.
1690 * Then see if no groups left, removes buddy completely.
1691 * Otherwise updates buddy groups on server.
1693 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1695 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1696 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1697 struct sipe_group *g = NULL;
1699 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1701 if (!b) return;
1703 if (group) {
1704 g = sipe_group_find_by_name(sip, group->name);
1707 if (g) {
1708 b->groups = g_slist_remove(b->groups, g);
1709 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1712 if (g_slist_length(b->groups) < 1) {
1713 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1714 sipe_cancel_scheduled_action(sip, action_name);
1715 g_free(action_name);
1717 g_hash_table_remove(sip->buddies, buddy->name);
1719 if (b->name) {
1720 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1721 send_soap_request(sip, body);
1722 g_free(body);
1725 g_free(b->name);
1726 g_free(b->annotation);
1727 g_free(b->device_name);
1728 g_slist_free(b->groups);
1729 g_free(b);
1730 } else {
1731 //updates groups on server
1732 sipe_group_set_user(sip, b->name);
1737 static void
1738 sipe_rename_group(PurpleConnection *gc,
1739 const char *old_name,
1740 PurpleGroup *group,
1741 GList *moved_buddies)
1743 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1744 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1745 if (group) {
1746 sipe_group_rename(sip, s_group, group->name);
1747 } else {
1748 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1752 static void
1753 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1755 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1756 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1757 if (s_group) {
1758 gchar *body;
1759 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1760 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1761 send_soap_request(sip, body);
1762 g_free(body);
1764 sip->groups = g_slist_remove(sip->groups, s_group);
1765 g_free(s_group->name);
1766 } else {
1767 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1771 static GList *sipe_status_types(PurpleAccount *acc)
1773 PurpleStatusType *type;
1774 GList *types = NULL;
1776 // Online
1777 type = purple_status_type_new_with_attrs(
1778 PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE,
1779 // Translators: noun
1780 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1781 NULL);
1782 types = g_list_append(types, type);
1784 // Busy
1785 type = purple_status_type_new_with_attrs(
1786 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1787 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1788 NULL);
1789 types = g_list_append(types, type);
1791 // Do Not Disturb (Not let user set it)
1792 type = purple_status_type_new_with_attrs(
1793 PURPLE_STATUS_UNAVAILABLE, "do-not-disturb", "Do Not Disturb", TRUE, FALSE, FALSE,
1794 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1795 NULL);
1796 types = g_list_append(types, type);
1798 // Be Right Back
1799 type = purple_status_type_new_with_attrs(
1800 PURPLE_STATUS_AWAY, "be-right-back", _("Be Right Back"), TRUE, TRUE, FALSE,
1801 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1802 NULL);
1803 types = g_list_append(types, type);
1805 // Away
1806 type = purple_status_type_new_with_attrs(
1807 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1808 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1809 NULL);
1810 types = g_list_append(types, type);
1812 //On The Phone
1813 type = purple_status_type_new_with_attrs(
1814 PURPLE_STATUS_UNAVAILABLE, "on-the-phone", _("On The Phone"), TRUE, TRUE, FALSE,
1815 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1816 NULL);
1817 types = g_list_append(types, type);
1819 //Out To Lunch
1820 type = purple_status_type_new_with_attrs(
1821 PURPLE_STATUS_AWAY, "out-to-lunch", "Out To Lunch", TRUE, TRUE, FALSE,
1822 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1823 NULL);
1824 types = g_list_append(types, type);
1826 //Appear Offline
1827 type = purple_status_type_new_full(
1828 PURPLE_STATUS_INVISIBLE, NULL, "Appear Offline", TRUE, TRUE, FALSE);
1829 types = g_list_append(types, type);
1831 // Offline
1832 type = purple_status_type_new_full(
1833 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1834 types = g_list_append(types, type);
1836 return types;
1840 * A callback for g_hash_table_foreach
1842 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1844 sipe_subscribe_to_name_single(sip, buddy->name);
1848 * Removes entries from purple buddy list
1849 * that does not correspond ones in the roaming contact list.
1851 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1852 GSList *buddies = purple_find_buddies(sip->account, NULL);
1853 GSList *entry = buddies;
1854 struct sipe_buddy *buddy;
1855 PurpleBuddy *b;
1856 PurpleGroup *g;
1858 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1859 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1860 while (entry) {
1861 b = entry->data;
1862 g = purple_buddy_get_group(b);
1863 buddy = g_hash_table_lookup(sip->buddies, b->name);
1864 if(buddy) {
1865 gboolean in_sipe_groups = FALSE;
1866 GSList *entry2 = buddy->groups;
1867 while (entry2) {
1868 struct sipe_group *group = entry2->data;
1869 if (!strcmp(group->name, g->name)) {
1870 in_sipe_groups = TRUE;
1871 break;
1873 entry2 = entry2->next;
1875 if(!in_sipe_groups) {
1876 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1877 purple_blist_remove_buddy(b);
1879 } else {
1880 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1881 purple_blist_remove_buddy(b);
1883 entry = entry->next;
1887 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1889 int len = msg->bodylen;
1891 gchar *tmp = sipmsg_find_header(msg, "Event");
1892 xmlnode *item;
1893 xmlnode *isc;
1894 gchar *contacts_delta;
1895 xmlnode *group_node;
1896 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1897 return FALSE;
1900 purple_debug_info("sipe", "msg->body:%s\n", msg->body);
1902 /* Convert the contact from XML to Purple Buddies */
1903 isc = xmlnode_from_str(msg->body, len);
1904 if (!isc) {
1905 return FALSE;
1908 contacts_delta = g_strdup(xmlnode_get_attrib(isc, "deltaNum"));
1909 if (contacts_delta) {
1910 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1913 /* Parse groups */
1914 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1915 struct sipe_group * group = g_new0(struct sipe_group, 1);
1917 group->name = g_strdup(xmlnode_get_attrib(group_node, "name"));
1918 if (!strncmp(group->name, "~", 1)){
1919 // TODO translate
1920 group->name = "Other Contacts";
1922 group->name = g_strdup(group->name);
1923 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1925 sipe_group_add(sip, group);
1928 // Make sure we have at least one group
1929 if (g_slist_length(sip->groups) == 0) {
1930 struct sipe_group * group = g_new0(struct sipe_group, 1);
1931 PurpleGroup *purple_group;
1932 // TODO translate
1933 group->name = g_strdup("Other Contacts");
1934 group->id = 1;
1935 purple_group = purple_group_new(group->name);
1936 purple_blist_add_group(purple_group, NULL);
1937 sip->groups = g_slist_append(sip->groups, group);
1940 /* Parse contacts */
1941 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1942 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1943 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1944 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1945 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1946 gchar **item_groups;
1947 struct sipe_group *group = NULL;
1948 struct sipe_buddy *buddy = NULL;
1949 int i = 0;
1951 // assign to group Other Contacts if nothing else received
1952 if(!groups || !strcmp("", groups) ) {
1953 group = sipe_group_find_by_name(sip, "Other Contacts");
1954 groups = group ? g_strdup_printf("%d", group->id) : "1";
1957 item_groups = g_strsplit(groups, " ", 0);
1959 while (item_groups[i]) {
1960 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1962 // If couldn't find the right group for this contact, just put them in the first group we have
1963 if (group == NULL && g_slist_length(sip->groups) > 0) {
1964 group = sip->groups->data;
1967 if (group != NULL) {
1968 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1969 if (!b){
1970 b = purple_buddy_new(sip->account, buddy_name, uri);
1971 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1974 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1975 if (name != NULL && strlen(name) != 0) {
1976 purple_blist_alias_buddy(b, name);
1980 if (!buddy) {
1981 buddy = g_new0(struct sipe_buddy, 1);
1982 buddy->name = g_strdup(b->name);
1983 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1986 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1988 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
1989 } else {
1990 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1991 name);
1994 i++;
1995 } // while, contact groups
1996 g_strfreev(item_groups);
1997 g_free(groups);
1998 g_free(name);
1999 g_free(buddy_name);
2000 g_free(uri);
2002 } // for, contacts
2004 xmlnode_free(isc);
2006 sipe_cleanup_local_blist(sip);
2008 //subscribe to buddies
2009 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2010 if(sip->msrtc_event_categories){
2011 sipe_subscribe_to_buddies_batched(sip);
2012 }else{
2013 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2015 sip->subscribed_buddies = TRUE;
2018 return 0;
2024 * Subscribe roaming contacts
2026 static void sipe_subscribe_buddylist(struct sipe_account_data *sip,struct sipmsg *msg)
2028 gchar *to = g_strdup_printf("sip:%s", sip->username);
2029 gchar *tmp = get_contact(sip);
2030 gchar *hdr = g_strdup_printf(
2031 "Event: vnd-microsoft-roaming-contacts\r\n"
2032 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2033 "Supported: com.microsoft.autoextend\r\n"
2034 "Supported: ms-benotify\r\n"
2035 "Proxy-Require: ms-benotify\r\n"
2036 "Supported: ms-piggyback-first-notify\r\n"
2037 "Contact: %s\r\n", tmp);
2038 g_free(tmp);
2040 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_roaming_contacts);
2041 g_free(to);
2042 g_free(hdr);
2045 static gboolean
2046 sipe_process_pending_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2048 sipe_process_incoming_pending (sip, msg);
2049 return TRUE;
2052 static void sipe_subscribe_pending_buddies(struct sipe_account_data *sip,struct sipmsg *msg)
2054 gchar *to = g_strdup_printf("sip:%s", sip->username);
2055 gchar *tmp = get_contact(sip);
2056 gchar *hdr = g_strdup_printf(
2057 "Event: presence.wpending\r\n"
2058 "Accept: text/xml+msrtc.wpending\r\n"
2059 "Supported: com.microsoft.autoextend\r\n"
2060 "Supported: ms-benotify\r\n"
2061 "Proxy-Require: ms-benotify\r\n"
2062 "Supported: ms-piggyback-first-notify\r\n"
2063 "Contact: %s\r\n", tmp);
2064 g_free(tmp);
2066 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_pending_response);
2067 g_free(to);
2068 g_free(hdr);
2071 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2073 const gchar *contacts_delta;
2074 xmlnode *xml;
2076 xml = xmlnode_from_str(msg->body, msg->bodylen);
2077 if (!xml)
2079 return;
2082 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2083 if (contacts_delta)
2085 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2088 xmlnode_free(xml);
2091 static gboolean
2092 sipe_process_acl_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2094 sipe_process_roaming_acl(sip, msg);
2095 return TRUE;
2099 * When we receive some self (BE) NOTIFY with a new subscriber
2100 * we sends a setSubscribers request to him [SIP-PRES]
2104 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2106 gchar *to;
2107 gchar *tmp;
2108 xmlnode *xml;
2109 xmlnode *node;
2110 const char *user;
2111 gchar *hdr;
2112 gchar *body;
2114 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2116 xml = xmlnode_from_str(msg->body, msg->bodylen);
2117 if (!xml) return;
2119 node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL);
2120 if (!node) {
2121 xmlnode_free(xml);
2122 return;
2125 user = xmlnode_get_attrib(node, "user");
2126 if (!user) {
2127 xmlnode_free(xml);
2128 return;
2131 to = g_strdup_printf("sip:%s", sip->username);
2132 tmp = get_contact(sip);
2133 hdr = g_strdup_printf(
2134 "Contact: %s\r\n"
2135 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", tmp);
2137 body = g_strdup_printf(
2138 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2139 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2140 "</setSubscribers>",user);
2142 g_free(tmp);
2143 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2144 g_free(body);
2145 g_free(to);
2146 g_free(hdr);
2147 xmlnode_free(node);
2150 static void sipe_subscribe_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2152 gchar *to = g_strdup_printf("sip:%s", sip->username);
2153 gchar *tmp = get_contact(sip);
2154 gchar *hdr = g_strdup_printf(
2155 "Event: vnd-microsoft-roaming-ACL\r\n"
2156 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2157 "Supported: com.microsoft.autoextend\r\n"
2158 "Supported: ms-benotify\r\n"
2159 "Proxy-Require: ms-benotify\r\n"
2160 "Supported: ms-piggyback-first-notify\r\n"
2161 "Contact: %s\r\n", tmp);
2162 g_free(tmp);
2164 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_acl_response);
2165 g_free(to);
2166 g_free(hdr);
2170 * To request for presence information about the user, access level settings that have already been configured by the user
2171 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2172 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2175 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2177 gchar *to = g_strdup_printf("sip:%s", sip->username);
2178 gchar *tmp = get_contact(sip);
2179 gchar *hdr = g_strdup_printf(
2180 "Event: vnd-microsoft-roaming-self\r\n"
2181 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2182 "Supported: com.microsoft.autoextend\r\n"
2183 "Supported: ms-benotify\r\n"
2184 "Proxy-Require: ms-benotify\r\n"
2185 "Supported: ms-piggyback-first-notify\r\n"
2186 "Contact: %s\r\n"
2187 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2189 gchar *body=g_strdup(
2190 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2191 "<roaming type=\"categories\"/>"
2192 "<roaming type=\"containers\"/>"
2193 "<roaming type=\"subscribers\"/></roamingList>");
2195 g_free(tmp);
2196 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
2197 g_free(body);
2198 g_free(to);
2199 g_free(hdr);
2202 /** Subscription for provisioning information to help with initial
2203 * configuration. This subscription is a one-time query (denoted by the Expires header,
2204 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2205 * configuration, meeting policies, and policy settings that Communicator must enforce.
2206 * TODO: for what we need this information.
2209 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2211 gchar *to = g_strdup_printf("sip:%s", sip->username);
2212 gchar *tmp = get_contact(sip);
2213 gchar *hdr = g_strdup_printf(
2214 "Event: vnd-microsoft-provisioning-v2\r\n"
2215 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2216 "Supported: com.microsoft.autoextend\r\n"
2217 "Supported: ms-benotify\r\n"
2218 "Proxy-Require: ms-benotify\r\n"
2219 "Supported: ms-piggyback-first-notify\r\n"
2220 "Expires: 0\r\n"
2221 "Contact: %s\r\n"
2222 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2223 gchar *body = g_strdup(
2224 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2225 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2226 "<provisioningGroup name=\"ucPolicy\"/>"
2227 "</provisioningGroupList>");
2229 g_free(tmp);
2230 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
2231 g_free(body);
2232 g_free(to);
2233 g_free(hdr);
2236 /* IM Session (INVITE and MESSAGE methods) */
2238 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2240 struct sip_im_session *session;
2241 GSList *entry;
2242 if (sip == NULL || who == NULL) {
2243 return NULL;
2246 entry = sip->im_sessions;
2247 while (entry) {
2248 session = entry->data;
2249 if ((who != NULL && !strcmp(who, session->with))) {
2250 return session;
2252 entry = entry->next;
2254 return NULL;
2257 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2259 struct sip_im_session *session = find_im_session(sip, who);
2260 if (!session) {
2261 session = g_new0(struct sip_im_session, 1);
2262 session->with = g_strdup(who);
2263 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2265 return session;
2268 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2270 struct sip_dialog *dialog = session->dialog;
2271 GSList *entry;
2273 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2275 if (dialog) {
2276 entry = dialog->routes;
2277 while (entry) {
2278 g_free(entry->data);
2279 entry = g_slist_remove(entry, entry->data);
2281 entry = dialog->supported;
2282 while (entry) {
2283 g_free(entry->data);
2284 entry = g_slist_remove(entry, entry->data);
2286 g_free(dialog->callid);
2287 g_free(dialog->ourtag);
2288 g_free(dialog->theirtag);
2289 g_free(dialog->theirepid);
2290 g_free(dialog->request);
2292 g_free(session->dialog);
2294 entry = session->outgoing_message_queue;
2295 while (entry) {
2296 g_free(entry->data);
2297 entry = g_slist_remove(entry, entry->data);
2300 g_free(session->with);
2301 g_free(session);
2304 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2306 char *msg, *msg_tmp;
2307 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2308 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2309 g_free(msg_tmp);
2310 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2311 "possibly because one or more persons are offline:\n%s") ,
2312 msg ? msg : "");
2313 purple_conv_present_error(with, sip->account, msg_tmp);
2314 g_free(msg);
2315 g_free(msg_tmp);
2318 static void sipe_im_remove_first_from_queue (struct sip_im_session * session);
2319 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2321 static gboolean
2322 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2324 gboolean ret = TRUE;
2325 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2326 struct sip_im_session * session = find_im_session(sip, with);
2327 struct sip_dialog *dialog;
2329 if (!session) {
2330 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2331 g_free(with);
2332 return FALSE;
2335 if (msg->response != 200) {
2336 gchar *queued_msg = NULL;
2337 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2339 if (session->outgoing_message_queue) {
2340 queued_msg = session->outgoing_message_queue->data;
2342 sipe_present_message_undelivered_err(with, sip, queued_msg);
2343 im_session_destroy(sip, session);
2344 g_free(with);
2345 return FALSE;
2348 dialog = session->dialog;
2349 if (!dialog) {
2350 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2351 ret = FALSE;
2354 sipe_im_remove_first_from_queue(session);
2355 sipe_im_process_queue(sip, session);
2356 g_free(with);
2357 return ret;
2360 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2362 gchar *hdr;
2363 gchar *fullto;
2364 gchar *tmp;
2365 char *msgformat;
2366 char *msgtext;
2367 gchar *msgr_value;
2368 gchar *msgr;
2370 if (strncmp("sip:", session->with, 4)) {
2371 fullto = g_strdup_printf("sip:%s", session->with);
2372 } else {
2373 fullto = g_strdup(session->with);
2376 sipe_parse_html(msg, &msgformat, &msgtext);
2377 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2379 msgr_value = sipmsg_get_msgr_string(msgformat);
2380 g_free(msgformat);
2381 if (msgr_value) {
2382 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2383 g_free(msgr_value);
2384 } else {
2385 msgr = g_strdup("");
2388 tmp = get_contact(sip);
2389 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2390 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2391 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2392 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2393 tmp, msgr);
2394 g_free(tmp);
2395 g_free(msgr);
2397 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2398 g_free(msgtext);
2399 g_free(hdr);
2400 g_free(fullto);
2404 static void
2405 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2407 GSList *entry = session->outgoing_message_queue;
2408 if (entry) {
2409 char *queued_msg = entry->data;
2410 sipe_send_message(sip, session, queued_msg);
2414 static void
2415 sipe_im_remove_first_from_queue (struct sip_im_session * session)
2417 if (session && session->outgoing_message_queue) {
2418 char *queued_msg = session->outgoing_message_queue->data;
2419 // Remove from the queue and free the string
2420 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2421 g_free(queued_msg);
2425 static void
2426 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2428 GSList *hdr = msg->headers;
2429 struct siphdrelement *elem;
2430 gchar *contact;
2432 while(hdr)
2434 elem = hdr->data;
2435 if(!strcmp(elem->name, "Record-Route"))
2437 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2438 dialog->routes = g_slist_append(dialog->routes, route);
2440 hdr = g_slist_next(hdr);
2443 if (outgoing)
2445 dialog->routes = g_slist_reverse(dialog->routes);
2448 if (dialog->routes)
2450 dialog->request = dialog->routes->data;
2451 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2454 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2455 dialog->routes = g_slist_append(dialog->routes, contact);
2458 static void
2459 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2461 GSList *hdr = msg->headers;
2462 struct siphdrelement *elem;
2463 while(hdr)
2465 elem = hdr->data;
2466 if(!strcmp(elem->name, "Supported")
2467 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2469 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2472 hdr = g_slist_next(hdr);
2476 static void
2477 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2479 gchar *us = outgoing ? "From" : "To";
2480 gchar *them = outgoing ? "To" : "From";
2482 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2483 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2484 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2485 if (!dialog->theirepid) {
2486 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2488 if (!dialog->theirepid) {
2489 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2492 sipe_get_route_header(msg, dialog, outgoing);
2493 sipe_get_supported_header(msg, dialog, outgoing);
2497 static gboolean
2498 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2500 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2501 struct sip_im_session * session = find_im_session(sip, with);
2502 struct sip_dialog *dialog;
2504 if (!session) {
2505 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2506 g_free(with);
2507 return FALSE;
2510 if (msg->response != 200) {
2511 gchar *queued_msg = NULL;
2512 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2514 if (session->outgoing_message_queue) {
2515 queued_msg = session->outgoing_message_queue->data;
2517 sipe_present_message_undelivered_err(with, sip, queued_msg);
2519 im_session_destroy(sip, session);
2520 g_free(with);
2521 return FALSE;
2524 dialog = session->dialog;
2525 if (!dialog) {
2526 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2527 g_free(with);
2528 return FALSE;
2531 sipe_parse_dialog(msg, dialog, TRUE);
2532 dialog->cseq = 0;
2534 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2535 session->outgoing_invite = NULL;
2536 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2537 sipe_im_remove_first_from_queue(session);
2538 } else {
2539 sipe_im_process_queue(sip, session);
2542 g_free(with);
2543 return TRUE;
2547 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
2549 gchar *hdr;
2550 gchar *to;
2551 gchar *contact;
2552 gchar *body;
2553 char *msgformat;
2554 char *msgtext;
2555 char *base64_msg;
2556 char *ms_text_format;
2557 gchar *msgr_value;
2558 gchar *msgr;
2560 if (session->dialog) {
2561 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2562 return;
2565 session->dialog = g_new0(struct sip_dialog, 1);
2567 if (strstr(session->with, "sip:")) {
2568 to = g_strdup(session->with);
2569 } else {
2570 to = g_strdup_printf("sip:%s", session->with);
2573 sipe_parse_html(msg_body, &msgformat, &msgtext);
2574 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2576 msgr_value = sipmsg_get_msgr_string(msgformat);
2577 g_free(msgformat);
2578 msgr = "";
2579 if (msgr_value) {
2580 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2581 g_free(msgr_value);
2584 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2585 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2586 g_free(msgtext);
2587 g_free(msgr);
2588 g_free(base64_msg);
2590 contact = get_contact(sip);
2591 hdr = g_strdup_printf(
2592 "Contact: %s\r\n%s"
2593 "Content-Type: application/sdp\r\n",
2594 contact, ms_text_format);
2595 g_free(ms_text_format);
2597 body = g_strdup_printf(
2598 "v=0\r\n"
2599 "o=- 0 0 IN IP4 %s\r\n"
2600 "s=session\r\n"
2601 "c=IN IP4 %s\r\n"
2602 "t=0 0\r\n"
2603 "m=message %d sip null\r\n"
2604 "a=accept-types:text/plain text/html image/gif "
2605 "multipart/alternative application/im-iscomposing+xml\r\n",
2606 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2608 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2609 to, to, hdr, body, session->dialog, process_invite_response);
2611 g_free(to);
2612 g_free(body);
2613 g_free(hdr);
2614 g_free(contact);
2617 static void
2618 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2620 if (session) {
2621 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2622 im_session_destroy(sip, session);
2626 static void
2627 sipe_convo_closed(PurpleConnection * gc, const char *who)
2629 struct sipe_account_data *sip = gc->proto_data;
2631 purple_debug_info("sipe", "conversation with %s closed\n", who);
2632 im_session_close(sip, find_im_session(sip, who));
2635 static void
2636 im_session_close_all (struct sipe_account_data *sip)
2638 GSList *entry = sip->im_sessions;
2639 while (entry) {
2640 im_session_close (sip, entry->data);
2641 entry = sip->im_sessions;
2645 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2647 struct sipe_account_data *sip;
2648 char *to;
2649 char *text;
2650 struct sip_im_session *session;
2652 purple_debug_info("sipe", "sipe_im_send what=%s\n", what);
2654 sip = gc->proto_data;
2655 to = g_strdup(who);
2656 text = g_strdup(what);
2658 session = find_or_create_im_session(sip, who);
2660 // Queue the message
2661 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
2663 if (session->dialog && session->dialog->callid) {
2664 sipe_im_process_queue(sip, session);
2665 } else if (!session->outgoing_invite) {
2666 // Need to send the INVITE to get the outgoing dialog setup
2667 sipe_invite(sip, session, text);
2670 g_free(to);
2671 return 1;
2675 /* End IM Session (INVITE and MESSAGE methods) */
2677 static unsigned int
2678 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2680 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2681 struct sip_im_session *session;
2683 if (state == PURPLE_NOT_TYPING)
2684 return 0;
2686 session = find_im_session(sip, who);
2688 if (session && session->dialog) {
2689 send_sip_request(gc, "INFO", who, who,
2690 "Content-Type: application/xml\r\n",
2691 SIPE_SEND_TYPING, session->dialog, NULL);
2694 return SIPE_TYPING_SEND_TIMEOUT;
2697 static gboolean resend_timeout(struct sipe_account_data *sip)
2699 GSList *tmp = sip->transactions;
2700 time_t currtime = time(NULL);
2701 while (tmp) {
2702 struct transaction *trans = tmp->data;
2703 tmp = tmp->next;
2704 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2705 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2706 /* TODO 408 */
2707 } else {
2708 if ((currtime - trans->time > 2) && trans->retries == 0) {
2709 trans->retries++;
2710 sendout_sipmsg(sip, trans->msg);
2714 return TRUE;
2717 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2719 /* register again when security token expires */
2720 /* we have to start a new authentication as the security token
2721 * is almost expired by sending a not signed REGISTER message */
2722 purple_debug_info("sipe", "do a full reauthentication\n");
2723 sipe_auth_free(&sip->registrar);
2724 sip->registerstatus = 0;
2725 do_register(sip);
2726 sip->reauthenticate_set = FALSE;
2729 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2731 gchar *from;
2732 gchar *contenttype;
2733 gboolean found = FALSE;
2735 from = parse_from(sipmsg_find_header(msg, "From"));
2737 if (!from) return;
2739 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2741 contenttype = sipmsg_find_header(msg, "Content-Type");
2742 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2743 gchar *msgr = sipmsg_find_part_of_header(contenttype, "msgr=", NULL, NULL);
2744 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2746 gchar *body_esc = g_markup_escape_text(msg->body, -1);
2747 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2748 g_free(msgr);
2749 g_free(body_esc);
2750 g_free(x_mms_im_format);
2752 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2753 g_free(body_html);
2754 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2755 found = TRUE;
2756 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2757 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2758 xmlnode *state;
2759 gchar *statedata;
2761 if (!isc) {
2762 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2763 return;
2766 state = xmlnode_get_child(isc, "state");
2768 if (!state) {
2769 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2770 xmlnode_free(isc);
2771 return;
2774 statedata = xmlnode_get_data(state);
2775 if (statedata) {
2776 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2777 else serv_got_typing_stopped(sip->gc, from);
2779 g_free(statedata);
2781 xmlnode_free(isc);
2782 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2783 found = TRUE;
2785 if (!found) {
2786 purple_debug_info("sipe", "got unknown mime-type");
2787 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2789 g_free(from);
2792 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2794 gchar *ms_text_format;
2795 gchar *from;
2796 struct sip_im_session *session;
2797 // Only accept text invitations
2798 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2799 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2800 return;
2803 from = parse_from(sipmsg_find_header(msg, "From"));
2804 session = find_or_create_im_session (sip, from);
2805 if (session) {
2806 if (session->dialog) {
2807 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2808 } else {
2809 session->dialog = g_new0(struct sip_dialog, 1);
2811 sipe_parse_dialog(msg, session->dialog, FALSE);
2813 session->dialog->callid = sipmsg_find_header(msg, "Call-ID");
2814 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2815 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2816 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2818 } else {
2819 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2822 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2823 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2824 if (ms_text_format && !strncmp(ms_text_format, "text/plain", 10)) {
2825 gchar *msgr = sipmsg_find_part_of_header(ms_text_format, "msgr=", ";", NULL);
2826 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2828 gchar *ms_body = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
2829 g_free(msgr);
2830 if (ms_body) {
2831 gchar *body = purple_base64_decode(ms_body, NULL);
2832 gchar *body_esc = g_markup_escape_text(body, -1);
2833 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2834 g_free(ms_body);
2835 g_free(body_esc);
2836 g_free(body);
2837 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2838 g_free(body_html);
2839 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2841 g_free(x_mms_im_format);
2843 g_free(from);
2845 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2846 sipmsg_remove_header(msg, "Ms-Text-Format");
2847 sipmsg_remove_header(msg, "EndPoints");
2848 sipmsg_remove_header(msg, "User-Agent");
2849 sipmsg_remove_header(msg, "Roster-Manager");
2851 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2852 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2854 send_sip_response(sip->gc, msg, 200, "OK", g_strdup_printf(
2855 "v=0\r\n"
2856 "o=- 0 0 IN IP4 %s\r\n"
2857 "s=session\r\n"
2858 "c=IN IP4 %s\r\n"
2859 "t=0 0\r\n"
2860 "m=message %d sip sip:%s\r\n"
2861 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2862 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2863 sip->realport, sip->username));
2866 static void sipe_connection_cleanup(struct sipe_account_data *);
2867 static void create_connection(struct sipe_account_data *, gchar *, int);
2869 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2871 gchar *tmp;
2872 //gchar krb5_token;
2873 const gchar *expires_header;
2874 int expires;
2875 GSList *hdr = msg->headers;
2876 struct siphdrelement *elem;
2878 expires_header = sipmsg_find_header(msg, "Expires");
2879 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2880 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2882 switch (msg->response) {
2883 case 200:
2884 if (expires == 0) {
2885 sip->registerstatus = 0;
2886 } else {
2887 int i = 0;
2888 gchar *contact_hdr = NULL;
2889 gchar *gruu = NULL;
2890 gchar *epid;
2891 gchar *uuid;
2893 sip->registerexpire = expires;
2895 if (!sip->reregister_set) {
2896 gchar *action_name = g_strdup_printf("<%s>", "registration");
2897 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
2898 g_free(action_name);
2899 sip->reregister_set = TRUE;
2902 sip->registerstatus = 3;
2904 if (!sip->reauthenticate_set) {
2905 /* we have to reauthenticate as our security token expires
2906 after eight hours (be five minutes early) */
2907 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
2908 guint reauth_timeout = (8 * 3600) - 360;
2909 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
2910 g_free(action_name);
2911 sip->reauthenticate_set = TRUE;
2914 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
2916 epid = get_epid();
2917 uuid = generateUUIDfromEPID(epid);
2918 g_free(epid);
2920 // There can be multiple Contact headers (one per location where the user is logged in) so
2921 // make sure to only get the one for this uuid
2922 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
2923 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
2924 if (valid_contact) {
2925 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
2926 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
2927 g_free(valid_contact);
2928 break;
2929 } else {
2930 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
2933 g_free(uuid);
2935 if(gruu) {
2936 sip->contact = g_strdup_printf("<%s>", gruu);
2937 g_free(gruu);
2938 } else {
2939 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
2940 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);
2942 sip->msrtc_event_categories = FALSE;
2944 while(hdr)
2946 elem = hdr->data;
2947 if(!strcmp(elem->name, "Supported"))
2949 if (strstr(elem->value, "msrtc-event-categories")){
2950 sip->msrtc_event_categories = TRUE;
2952 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s, %d\n", elem->value, sip->msrtc_event_categories);
2954 hdr = g_slist_next(hdr);
2957 if (!sip->subscribed) { //do it just once, not every re-register
2958 tmp = sipmsg_find_header(msg, "Allow-Events");
2959 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
2960 sipe_subscribe_buddylist(sip, msg);
2962 sipe_subscribe_acl(sip, msg);
2963 sipe_subscribe_roaming_self(sip, msg);
2964 sipe_subscribe_roaming_provisioning(sip, msg);
2965 sipe_subscribe_pending_buddies(sip, msg);
2966 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
2967 sip->subscribed = TRUE;
2970 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
2971 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
2972 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
2973 } else {
2974 tmp = sipmsg_find_header(msg, "ms-keep-alive");
2975 if (tmp) {
2976 sipe_keep_alive_timeout(sip, tmp);
2980 // Should we remove the transaction here?
2981 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
2982 transactions_remove(sip, tc);
2984 break;
2985 case 301:
2987 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
2989 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
2990 gchar **parts = g_strsplit(redirect + 4, ";", 0);
2991 gchar **tmp;
2992 gchar *hostname;
2993 int port = 0;
2994 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
2995 int i = 1;
2997 tmp = g_strsplit(parts[0], ":", 0);
2998 hostname = g_strdup(tmp[0]);
2999 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3000 g_strfreev(tmp);
3002 while (parts[i]) {
3003 tmp = g_strsplit(parts[i], "=", 0);
3004 if (tmp[1]) {
3005 if (g_strcasecmp("transport", tmp[0]) == 0) {
3006 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3007 transport = SIPE_TRANSPORT_TCP;
3008 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3009 transport = SIPE_TRANSPORT_UDP;
3013 g_strfreev(tmp);
3014 i++;
3016 g_strfreev(parts);
3018 /* Close old connection */
3019 sipe_connection_cleanup(sip);
3021 /* Create new connection */
3022 sip->transport = transport;
3023 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3024 hostname, port, TRANSPORT_DESCRIPTOR);
3025 create_connection(sip, hostname, port);
3028 break;
3029 case 401:
3030 if (sip->registerstatus != 2) {
3031 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3032 if (sip->registrar.retries > 3) {
3033 sip->gc->wants_to_die = TRUE;
3034 purple_connection_error(sip->gc, _("Wrong Password"));
3035 return TRUE;
3037 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3038 tmp = sipmsg_find_auth_header(msg, "NTLM");
3039 } else {
3040 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3042 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3043 fill_auth(sip, tmp, &sip->registrar);
3044 sip->registerstatus = 2;
3045 if (sip->account->disconnecting) {
3046 do_register_exp(sip, 0);
3047 } else {
3048 do_register(sip);
3051 break;
3052 case 403:
3054 const gchar *warning = sipmsg_find_header(msg, "Warning");
3055 if (warning != NULL) {
3056 /* Example header:
3057 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3059 gchar **tmp = g_strsplit(warning, "\"", 0);
3060 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3061 g_strfreev(tmp);
3062 } else {
3063 warning = _("You have been rejected by the server");
3066 sip->gc->wants_to_die = TRUE;
3067 purple_connection_error(sip->gc, warning);
3068 return TRUE;
3070 break;
3071 case 404:
3073 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3074 if (warning != NULL) {
3075 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3076 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3077 g_free(reason);
3078 } else {
3079 warning = _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3082 sip->gc->wants_to_die = TRUE;
3083 purple_connection_error(sip->gc, warning);
3084 return TRUE;
3086 break;
3087 case 503:
3089 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3090 if (warning != NULL) {
3091 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3092 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3093 g_free(reason);
3094 } else {
3095 warning = _("Service unavailable: no reason given");
3098 sip->gc->wants_to_die = TRUE;
3099 purple_connection_error(sip->gc, warning);
3100 return TRUE;
3102 break;
3104 return TRUE;
3107 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3109 const char *uri;
3110 xmlnode *xn_categories;
3111 xmlnode *xn_category;
3112 xmlnode *xn_node;
3113 int changed = 0;
3114 const char *activity = NULL;
3116 xn_categories = xmlnode_from_str(data, len);
3117 uri = xmlnode_get_attrib(xn_categories, "uri");
3119 purple_debug_info("sipe", "process_incoming_notify_rlmi\n");
3121 for (xn_category = xmlnode_get_child(xn_categories, "category");
3122 xn_category ;
3123 xn_category = xmlnode_get_next_twin(xn_category) )
3125 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3127 if (!strcmp(attrVar, "note"))
3129 char *note;
3130 struct sipe_buddy *sbuddy;
3131 xn_node = xmlnode_get_child(xn_category, "note");
3132 if (!xn_node) continue;
3133 xn_node = xmlnode_get_child(xn_node, "body");
3134 if (!xn_node) continue;
3136 note = xmlnode_get_data(xn_node);
3138 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3139 if (sbuddy && note)
3141 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3142 sbuddy->annotation = g_strdup(note);
3143 changed = 1;
3146 g_free(note);
3148 else if(!strcmp(attrVar, "state"))
3150 char *data;
3151 int avail;
3152 xn_node = xmlnode_get_child(xn_category, "state");
3153 if (!xn_node) continue;
3154 xn_node = xmlnode_get_child(xn_node, "availability");
3155 if (!xn_node) continue;
3157 data = xmlnode_get_data(xn_node);
3158 avail = atoi(data);
3159 g_free(data);
3161 if (avail < 3000)
3162 activity = "unknown";
3163 else if (avail < 4500)
3164 activity = "available";
3165 else if (avail < 6000)
3166 activity = "idle";
3167 else if (avail < 7500)
3168 activity = "busy";
3169 else if (avail < 9000)
3170 activity = "busy";
3171 else if (avail < 12000)
3172 activity = "dnd";
3173 else if (avail < 18000)
3174 activity = "away";
3175 else
3176 activity = "offline";
3178 changed = 1;
3181 if (changed)
3183 if(activity){
3184 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3188 xmlnode_free(xn_categories);
3191 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3193 const char *uri,*state;
3194 xmlnode *xn_list;
3195 xmlnode *xn_resource;
3196 xmlnode *xn_instance;
3198 xn_list = xmlnode_from_str(data, len);
3200 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3201 xn_resource;
3202 xn_resource = xmlnode_get_next_twin(xn_resource) )
3204 xn_instance = xmlnode_get_child(xn_resource, "instance");
3205 if (!xn_instance) return;
3207 state = xmlnode_get_attrib(xn_instance, "state");
3208 uri = xmlnode_get_attrib(xn_instance, "cid");
3209 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3210 if(strstr(state,"resubscribe")){
3211 sipe_subscribe_to_name_single(sip, uri);
3216 static void process_incoming_notify_pidf(struct sipe_account_data *sip, struct sipmsg *msg)
3218 const gchar *uri;
3219 gchar *getbasic = g_strdup("closed");
3220 gchar *activity = g_strdup("available");
3221 xmlnode *pidf;
3222 xmlnode *basicstatus = NULL, *tuple, *status;
3223 gboolean isonline = FALSE;
3224 xmlnode *display_name_node;
3226 pidf = xmlnode_from_str(msg->body, msg->bodylen);
3227 if (!pidf) {
3228 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",msg->body);
3229 return;
3232 uri = xmlnode_get_attrib(pidf, "entity");
3234 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3236 if ((status = xmlnode_get_child(tuple, "status"))) {
3237 basicstatus = xmlnode_get_child(status, "basic");
3241 if (!basicstatus) {
3242 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3243 xmlnode_free(pidf);
3244 return;
3247 getbasic = xmlnode_get_data(basicstatus);
3248 if (!getbasic) {
3249 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3250 xmlnode_free(pidf);
3251 return;
3254 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3255 if (strstr(getbasic, "open")) {
3256 isonline = TRUE;
3259 display_name_node = xmlnode_get_child(pidf, "display-name");
3260 // updating display name if alias was just URI
3261 if (display_name_node) {
3262 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3263 GSList *entry = buddies;
3264 PurpleBuddy *p_buddy;
3265 char * display_name = xmlnode_get_data(display_name_node);
3267 while (entry) {
3268 const char *server_alias;
3269 char *alias;
3271 p_buddy = entry->data;
3273 alias = (char *)purple_buddy_get_alias(p_buddy);
3274 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3275 if (!g_ascii_strcasecmp(uri, alias)) {
3276 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3277 purple_blist_alias_buddy(p_buddy, display_name);
3279 g_free(alias);
3281 server_alias = purple_buddy_get_server_alias(p_buddy);
3282 if (display_name &&
3283 ( (server_alias && strcmp(display_name, server_alias))
3284 || !server_alias || strlen(server_alias) == 0 )
3286 purple_blist_server_alias_buddy(p_buddy, display_name);
3289 entry = entry->next;
3293 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3294 if ((status = xmlnode_get_child(tuple, "status"))) {
3295 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3296 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3297 activity = xmlnode_get_data(basicstatus);
3303 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3305 if (isonline) {
3306 gchar * status_id = NULL;
3307 if (activity) {
3308 if (strstr(activity, "busy")) {
3309 status_id = "busy";
3310 } else if (strstr(activity, "away")) {
3311 status_id = "away";
3315 if (!status_id) {
3316 status_id = "available";
3319 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3320 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3321 } else {
3322 purple_prpl_got_user_status(sip->account, uri, "offline", NULL);
3325 xmlnode_free(pidf);
3326 g_free(getbasic);
3327 g_free(activity);
3330 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, struct sipmsg *msg)
3332 const char *availability;
3333 const char *activity;
3334 const char *display_name = NULL;
3335 const char *activity_name;
3336 const char *name;
3337 char *uri;
3338 int avl;
3339 int act;
3340 struct sipe_buddy *sbuddy;
3342 xmlnode *xn_presentity = xmlnode_from_str(msg->body, msg->bodylen);
3344 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3345 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3346 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3347 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3348 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3349 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3350 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3351 const char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3352 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3353 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3354 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3355 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3357 name = xmlnode_get_attrib(xn_presentity, "uri");
3358 uri = g_strdup_printf("sip:%s", name);
3359 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3360 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3362 // updating display name if alias was just URI
3363 if (xn_display_name) {
3364 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3365 GSList *entry = buddies;
3366 PurpleBuddy *p_buddy;
3367 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3369 while (entry) {
3370 const char *email_str, *server_alias;
3372 p_buddy = entry->data;
3374 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3375 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3376 purple_blist_alias_buddy(p_buddy, display_name);
3379 server_alias = purple_buddy_get_server_alias(p_buddy);
3380 if (display_name &&
3381 ( (server_alias && strcmp(display_name, server_alias))
3382 || !server_alias || strlen(server_alias) == 0 )
3384 purple_blist_server_alias_buddy(p_buddy, display_name);
3387 if (email) {
3388 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3389 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3390 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3394 entry = entry->next;
3398 avl = atoi(availability);
3399 act = atoi(activity);
3401 if (act <= 100)
3402 activity_name = "away";
3403 else if (act <= 150)
3404 activity_name = "out-to-lunch";
3405 else if (act <= 300)
3406 activity_name = "be-right-back";
3407 else if (act <= 400)
3408 activity_name = "available";
3409 else if (act <= 500)
3410 activity_name = "on-the-phone";
3411 else if (act <= 600)
3412 activity_name = "busy";
3413 else
3414 activity_name = "available";
3416 if (avl == 0)
3417 activity_name = "offline";
3419 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3420 if (sbuddy)
3422 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3423 sbuddy->annotation = NULL;
3424 if (note) { sbuddy->annotation = g_strdup(note); }
3426 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3427 sbuddy->device_name = NULL;
3428 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3431 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3432 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3433 xmlnode_free(xn_presentity);
3434 g_free(uri);
3437 static void process_incoming_notify_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3439 char *ctype = sipmsg_find_header(msg, "Content-Type");
3441 purple_debug_info("sipe", "process_incoming_notify_presence: Content-Type: %s\n", ctype ? ctype : "");
3443 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3444 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3446 const char *content = msg->body;
3447 unsigned length = msg->bodylen;
3448 PurpleMimeDocument *mime = NULL;
3450 if (strstr(ctype, "multipart"))
3452 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3453 const char *content_type;
3454 GList* parts;
3455 mime = purple_mime_document_parse(doc);
3456 parts = purple_mime_document_get_parts(mime);
3457 while(parts){
3458 content = purple_mime_part_get_data(parts->data);
3459 length = purple_mime_part_get_length(parts->data);
3460 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3461 if(content_type && strstr(content_type,"application/rlmi+xml"))
3463 process_incoming_notify_rlmi_resub(sip, content, length);
3465 else
3467 process_incoming_notify_rlmi(sip, content, length);
3469 parts = parts->next;
3471 g_free(doc);
3473 if (mime)
3475 purple_mime_document_free(mime);
3478 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3480 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3482 else if(strstr(ctype, "application/rlmi+xml"))
3484 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3487 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3489 process_incoming_notify_msrtc(sip, msg);
3491 else
3493 process_incoming_notify_pidf(sip, msg);
3498 * Dispatcher for all incoming subscription information
3499 * whether it comes from NOTIFY, BENOTIFY requests or
3500 * piggy-backed to subscription's OK responce.
3502 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3503 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3505 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3507 gchar *event = sipmsg_find_header(msg, "Event");
3508 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3509 const char *uri,*state;
3510 xmlnode *xn_list;
3511 xmlnode *xn_resource;
3512 xmlnode *xn_instance;
3514 int expires = 0;
3516 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3517 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3519 if (!request)
3521 const gchar *expires_header;
3522 expires_header = sipmsg_find_header(msg, "Expires");
3523 expires = expires_header ? strtol(expires_header, NULL, 10) : 0;
3524 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires);
3527 if (!subscription_state || strstr(subscription_state, "active"))
3529 if (event && strstr(event, "presence"))
3531 process_incoming_notify_presence(sip, msg);
3533 else if (event && strstr(event, "vnd-microsoft-roaming-contacts"))
3535 sipe_process_roaming_contacts(sip, msg, NULL);
3537 else if (event && strstr(event, "vnd-microsoft-roaming-self"))
3539 sipe_process_roaming_self(sip, msg);
3541 else if (event && strstr(event, "vnd-microsoft-roaming-ACL"))
3543 sipe_process_roaming_acl(sip, msg);
3545 else if (event && strstr(event, "presence.wpending"))
3547 sipe_process_incoming_pending(sip, msg);
3549 else
3551 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3555 //The server sends a (BE)NOTIFY with the status 'terminated'
3556 if(request && subscription_state && strstr(subscription_state, "terminated") )
3558 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3559 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3560 g_free(from);
3563 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3564 if (request && !benotify)
3566 sipmsg_remove_header(msg, "Expires");
3567 sipmsg_remove_header(msg, "subscription-state");
3568 sipmsg_remove_header(msg, "Event");
3569 sipmsg_remove_header(msg, "Require");
3570 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3575 * unused. Needed?
3577 static gchar* gen_xpidf(struct sipe_account_data *sip)
3579 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3580 "<presence>\r\n"
3581 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3582 "<display name=\"sip:%s\"/>\r\n"
3583 "<atom id=\"1234\">\r\n"
3584 "<address uri=\"sip:%s\">\r\n"
3585 "<status status=\"%s\"/>\r\n"
3586 "</address>\r\n"
3587 "</atom>\r\n"
3588 "</presence>\r\n",
3589 sip->username,
3590 sip->username,
3591 sip->username,
3592 sip->status);
3593 return doc;
3598 static gchar* gen_pidf(struct sipe_account_data *sip)
3600 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3601 "<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"
3602 "<tuple id=\"0\">\r\n"
3603 "<status>\r\n"
3604 "<basic>open</basic>\r\n"
3605 "<ep:activities>\r\n"
3606 " <ep:activity>%s</ep:activity>\r\n"
3607 "</ep:activities>"
3608 "</status>\r\n"
3609 "</tuple>\r\n"
3610 "<ci:display-name>%s</ci:display-name>\r\n"
3611 "</presence>",
3612 sip->username,
3613 sip->status,
3614 sip->username);
3615 return doc;
3619 static gboolean
3620 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3622 if (msg->response == 488) {
3623 sip->presence_method_version = 1;
3624 send_presence_info(sip);
3626 return TRUE;
3629 static void send_presence_info_v0(struct sipe_account_data *sip, char * note)
3631 int availability = 300; // online
3632 int activity = 400; // Available
3633 gchar *name;
3634 gchar *body;
3635 if (!strcmp(sip->status, "away")) {
3636 activity = 100;
3637 } else if (!strcmp(sip->status, "out-to-lunch")) {
3638 activity = 150;
3639 } else if (!strcmp(sip->status, "be-right-back")) {
3640 activity = 300;
3641 } else if (!strcmp(sip->status, "on-the-phone")) {
3642 activity = 500;
3643 } else if (!strcmp(sip->status, "do-not-disturb")) {
3644 activity = 600;
3645 } else if (!strcmp(sip->status, "busy")) {
3646 activity = 600;
3647 } else if (!strcmp(sip->status, "invisible")) {
3648 availability = 0; // offline
3649 activity = 100;
3652 name = g_strdup_printf("sip: sip:%s", sip->username);
3653 //@TODO: send user data - state; add hostname in upper case
3654 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3655 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
3656 g_free(name);
3657 g_free(body);
3660 static gboolean
3661 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3663 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3664 if (msg->response == 200) {
3665 sip->status_version = 0;
3666 send_presence_info(sip);
3668 return TRUE;
3671 static gboolean
3672 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3674 if (msg->response == 409) {
3675 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3676 // TODO need to parse the version #'s?
3677 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3678 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3679 gchar *tmp;
3680 gchar *hdr;
3682 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg->body);
3684 tmp = get_contact(sip);
3685 hdr = g_strdup_printf("Contact: %s\r\n"
3686 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3688 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3690 g_free(tmp);
3691 g_free(hdr);
3692 g_free(uri);
3693 g_free(doc);
3695 return TRUE;
3698 static void send_presence_info_v1(struct sipe_account_data *sip, char * note)
3700 int code;
3701 gchar *uri;
3702 gchar *doc;
3703 gchar *tmp;
3704 gchar *hdr;
3705 if (!strcmp(sip->status, "away")) {
3706 code = 12000;
3707 } else if (!strcmp(sip->status, "busy")) {
3708 code = 6000;
3709 } else {
3710 // Available
3711 code = 3000;
3714 uri = g_strdup_printf("sip:%s", sip->username);
3715 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3716 sip->status_version, code,
3717 sip->status_version, code,
3718 sip->status_version, note ? note : "",
3719 sip->status_version, note ? note : "",
3720 sip->status_version, note ? note : ""
3722 sip->status_version++;
3724 tmp = get_contact(sip);
3725 hdr = g_strdup_printf("Contact: %s\r\n"
3726 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3728 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
3730 g_free(tmp);
3731 g_free(hdr);
3732 g_free(uri);
3733 g_free(doc);
3736 static void send_presence_info(struct sipe_account_data *sip)
3738 PurpleStatus * status = purple_account_get_active_status(sip->account);
3739 gchar *note;
3740 if (!status) return;
3742 note = g_strdup(purple_status_get_attr_string(status, "message"));
3744 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
3745 if (sip->presence_method_version != 1) {
3746 send_presence_info_v0(sip, note);
3747 } else {
3748 send_presence_info_v1(sip, note);
3752 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3754 gboolean found = FALSE;
3755 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3756 if (msg->response == 0) { /* request */
3757 if (!strcmp(msg->method, "MESSAGE")) {
3758 process_incoming_message(sip, msg);
3759 found = TRUE;
3760 } else if (!strcmp(msg->method, "NOTIFY")) {
3761 purple_debug_info("sipe","send->process_incoming_notify\n");
3762 process_incoming_notify(sip, msg, TRUE, FALSE);
3763 found = TRUE;
3764 } else if (!strcmp(msg->method, "BENOTIFY")) {
3765 purple_debug_info("sipe","send->process_incoming_benotify\n");
3766 process_incoming_notify(sip, msg, TRUE, TRUE);
3767 found = TRUE;
3768 } else if (!strcmp(msg->method, "INVITE")) {
3769 process_incoming_invite(sip, msg);
3770 found = TRUE;
3771 } else if (!strcmp(msg->method, "INFO")) {
3772 // TODO needs work
3773 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3774 if (from) {
3775 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3777 g_free(from);
3778 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3779 found = TRUE;
3780 } else if (!strcmp(msg->method, "ACK")) {
3781 // ACK's don't need any response
3782 found = TRUE;
3783 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3784 // LCS 2005 sends us these - just respond 200 OK
3785 found = TRUE;
3786 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3787 } else if (!strcmp(msg->method, "BYE")) {
3788 struct sip_im_session *session;
3789 gchar *from;
3790 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3792 from = parse_from(sipmsg_find_header(msg, "From"));
3793 session = find_im_session (sip, from);
3794 g_free(from);
3796 if (session) {
3797 // TODO Let the user know the other user left the conversation?
3798 im_session_destroy(sip, session);
3801 found = TRUE;
3802 } else {
3803 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3805 } else { /* response */
3806 struct transaction *trans = transactions_find(sip, msg);
3807 if (trans) {
3808 if (msg->response == 407) {
3809 gchar *resend, *auth, *ptmp;
3811 if (sip->proxy.retries > 30) return;
3812 sip->proxy.retries++;
3813 /* do proxy authentication */
3815 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3817 fill_auth(sip, ptmp, &sip->proxy);
3818 auth = auth_header(sip, &sip->proxy, trans->msg);
3819 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3820 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
3821 g_free(auth);
3822 resend = sipmsg_to_string(trans->msg);
3823 /* resend request */
3824 sendout_pkt(sip->gc, resend);
3825 g_free(resend);
3826 } else {
3827 if (msg->response == 100 || msg->response == 180) {
3828 /* ignore provisional response */
3829 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
3830 } else {
3831 sip->proxy.retries = 0;
3832 if (!strcmp(trans->msg->method, "REGISTER")) {
3833 if (msg->response == 401)
3835 sip->registrar.retries++;
3836 sip->registrar.expires = 0;
3838 else
3840 sip->registrar.retries = 0;
3842 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
3843 } else {
3844 if (msg->response == 401) {
3845 gchar *resend, *auth, *ptmp;
3847 if (sip->registrar.retries > 4) return;
3848 sip->registrar.retries++;
3850 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3851 ptmp = sipmsg_find_auth_header(msg, "NTLM");
3852 } else {
3853 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
3856 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
3858 fill_auth(sip, ptmp, &sip->registrar);
3859 auth = auth_header(sip, &sip->registrar, trans->msg);
3860 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3861 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
3863 //sipmsg_remove_header(trans->msg, "Authorization");
3864 //sipmsg_add_header(trans->msg, "Authorization", auth);
3865 g_free(auth);
3866 resend = sipmsg_to_string(trans->msg);
3867 /* resend request */
3868 sendout_pkt(sip->gc, resend);
3869 g_free(resend);
3873 if (trans->callback) {
3874 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
3875 /* call the callback to process response*/
3876 (trans->callback)(sip, msg, trans);
3878 /* Not sure if this is needed or what needs to be done
3879 but transactions seem to be removed prematurely so
3880 this only removes them if the response is 200 OK */
3881 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
3882 /*Has a bug and it's unneccesary*/
3883 /*transactions_remove(sip, trans);*/
3887 found = TRUE;
3888 } else {
3889 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
3892 if (!found) {
3893 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
3897 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
3899 char *cur;
3900 char *dummy;
3901 struct sipmsg *msg;
3902 int restlen;
3903 cur = conn->inbuf;
3905 /* according to the RFC remove CRLF at the beginning */
3906 while (*cur == '\r' || *cur == '\n') {
3907 cur++;
3909 if (cur != conn->inbuf) {
3910 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
3911 conn->inbufused = strlen(conn->inbuf);
3914 /* Received a full Header? */
3915 sip->processing_input = TRUE;
3916 while (sip->processing_input &&
3917 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
3918 time_t currtime = time(NULL);
3919 cur += 2;
3920 cur[0] = '\0';
3921 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
3922 msg = sipmsg_parse_header(conn->inbuf);
3923 cur[0] = '\r';
3924 cur += 2;
3925 restlen = conn->inbufused - (cur - conn->inbuf);
3926 if (restlen >= msg->bodylen) {
3927 dummy = g_malloc(msg->bodylen + 1);
3928 memcpy(dummy, cur, msg->bodylen);
3929 dummy[msg->bodylen] = '\0';
3930 msg->body = dummy;
3931 cur += msg->bodylen;
3932 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
3933 conn->inbufused = strlen(conn->inbuf);
3934 } else {
3935 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
3936 restlen, msg->bodylen, (int)strlen(conn->inbuf));
3937 sipmsg_free(msg);
3938 return;
3941 /*if (msg->body) {
3942 purple_debug_info("sipe", "body:\n%s", msg->body);
3945 // Verify the signature before processing it
3946 if (sip->registrar.ntlm_key) {
3947 struct sipmsg_breakdown msgbd;
3948 gchar *signature_input_str;
3949 gchar *signature = NULL;
3950 gchar *rspauth;
3951 msgbd.msg = msg;
3952 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
3953 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
3954 if (signature_input_str != NULL) {
3955 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
3957 g_free(signature_input_str);
3959 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
3961 if (signature != NULL) {
3962 if (rspauth != NULL) {
3963 if (purple_ntlm_verify_signature (signature, rspauth)) {
3964 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
3965 process_input_message(sip, msg);
3966 } else {
3967 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
3968 purple_connection_error(sip->gc, _("Invalid message signature received"));
3969 sip->gc->wants_to_die = TRUE;
3971 } else if (msg->response == 401) {
3972 purple_connection_error(sip->gc, _("Wrong Password"));
3973 sip->gc->wants_to_die = TRUE;
3975 g_free(signature);
3978 g_free(rspauth);
3979 sipmsg_breakdown_free(&msgbd);
3980 } else {
3981 process_input_message(sip, msg);
3984 sipmsg_free(msg);
3988 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
3990 PurpleConnection *gc = data;
3991 struct sipe_account_data *sip = gc->proto_data;
3992 struct sipmsg *msg;
3993 int len;
3994 time_t currtime;
3996 static char buffer[65536];
3997 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
3998 buffer[len] = '\0';
3999 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4000 msg = sipmsg_parse_msg(buffer);
4001 if (msg) process_input_message(sip, msg);
4005 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4007 struct sipe_account_data *sip = gc->proto_data;
4008 PurpleSslConnection *gsc = sip->gsc;
4010 purple_debug_error("sipe", "%s",debug);
4011 purple_connection_error(gc, msg);
4013 /* Invalidate this connection. Next send will open a new one */
4014 if (gsc) {
4015 connection_remove(sip, gsc->fd);
4016 purple_ssl_close(gsc);
4018 sip->gsc = NULL;
4019 sip->fd = -1;
4022 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4024 PurpleConnection *gc = data;
4025 struct sipe_account_data *sip;
4026 struct sip_connection *conn;
4027 int readlen, len;
4028 gboolean firstread = TRUE;
4030 /* NOTE: This check *IS* necessary */
4031 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4032 purple_ssl_close(gsc);
4033 return;
4036 sip = gc->proto_data;
4037 conn = connection_find(sip, gsc->fd);
4038 if (conn == NULL) {
4039 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4040 gc->wants_to_die = TRUE;
4041 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4042 return;
4045 /* Read all available data from the SSL connection */
4046 do {
4047 /* Increase input buffer size as needed */
4048 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4049 conn->inbuflen += SIMPLE_BUF_INC;
4050 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4051 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4054 /* Try to read as much as there is space left in the buffer */
4055 readlen = conn->inbuflen - conn->inbufused - 1;
4056 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4058 if (len < 0 && errno == EAGAIN) {
4059 /* Try again later */
4060 return;
4061 } else if (len < 0) {
4062 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4063 return;
4064 } else if (firstread && (len == 0)) {
4065 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4066 return;
4069 conn->inbufused += len;
4070 firstread = FALSE;
4072 /* Equivalence indicates that there is possibly more data to read */
4073 } while (len == readlen);
4075 conn->inbuf[conn->inbufused] = '\0';
4076 process_input(sip, conn);
4080 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4082 PurpleConnection *gc = data;
4083 struct sipe_account_data *sip = gc->proto_data;
4084 int len;
4085 struct sip_connection *conn = connection_find(sip, source);
4086 if (!conn) {
4087 purple_debug_error("sipe", "Connection not found!\n");
4088 return;
4091 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4092 conn->inbuflen += SIMPLE_BUF_INC;
4093 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4096 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4098 if (len < 0 && errno == EAGAIN)
4099 return;
4100 else if (len <= 0) {
4101 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4102 connection_remove(sip, source);
4103 if (sip->fd == source) sip->fd = -1;
4104 return;
4107 conn->inbufused += len;
4108 conn->inbuf[conn->inbufused] = '\0';
4110 process_input(sip, conn);
4113 /* Callback for new connections on incoming TCP port */
4114 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4116 PurpleConnection *gc = data;
4117 struct sipe_account_data *sip = gc->proto_data;
4118 struct sip_connection *conn;
4120 int newfd = accept(source, NULL, NULL);
4122 conn = connection_create(sip, newfd);
4124 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4127 static void login_cb(gpointer data, gint source, const gchar *error_message)
4129 PurpleConnection *gc = data;
4130 struct sipe_account_data *sip;
4131 struct sip_connection *conn;
4133 if (!PURPLE_CONNECTION_IS_VALID(gc))
4135 if (source >= 0)
4136 close(source);
4137 return;
4140 if (source < 0) {
4141 purple_connection_error(gc, _("Could not connect"));
4142 return;
4145 sip = gc->proto_data;
4146 sip->fd = source;
4147 sip->last_keepalive = time(NULL);
4149 conn = connection_create(sip, source);
4151 do_register(sip);
4153 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4156 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4158 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4159 if (sip == NULL) return;
4161 do_register(sip);
4164 static guint sipe_ht_hash_nick(const char *nick)
4166 char *lc = g_utf8_strdown(nick, -1);
4167 guint bucket = g_str_hash(lc);
4168 g_free(lc);
4170 return bucket;
4173 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4175 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4178 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4180 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4182 sip->listen_data = NULL;
4184 if (listenfd == -1) {
4185 purple_connection_error(sip->gc, _("Could not create listen socket"));
4186 return;
4189 sip->fd = listenfd;
4191 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4192 sip->listenfd = sip->fd;
4194 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4196 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4197 do_register(sip);
4200 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4202 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4203 int addr_size;
4205 sip->query_data = NULL;
4207 if (!hosts || !hosts->data) {
4208 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4209 return;
4212 addr_size = GPOINTER_TO_INT(hosts->data);
4213 hosts = g_slist_remove(hosts, hosts->data);
4214 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4215 g_free(hosts->data);
4216 hosts = g_slist_remove(hosts, hosts->data);
4217 while (hosts) {
4218 hosts = g_slist_remove(hosts, hosts->data);
4219 g_free(hosts->data);
4220 hosts = g_slist_remove(hosts, hosts->data);
4223 /* create socket for incoming connections */
4224 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4225 sipe_udp_host_resolved_listen_cb, sip);
4226 if (sip->listen_data == NULL) {
4227 purple_connection_error(sip->gc, _("Could not create listen socket"));
4228 return;
4232 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4233 gpointer data)
4235 PurpleConnection *gc = data;
4236 struct sipe_account_data *sip;
4238 /* If the connection is already disconnected, we don't need to do anything else */
4239 if (!PURPLE_CONNECTION_IS_VALID(gc))
4240 return;
4242 sip = gc->proto_data;
4243 sip->fd = -1;
4244 sip->gsc = NULL;
4246 switch(error) {
4247 case PURPLE_SSL_CONNECT_FAILED:
4248 purple_connection_error(gc, _("Connection Failed"));
4249 break;
4250 case PURPLE_SSL_HANDSHAKE_FAILED:
4251 purple_connection_error(gc, _("SSL Handshake Failed"));
4252 break;
4253 case PURPLE_SSL_CERTIFICATE_INVALID:
4254 purple_connection_error(gc, _("SSL Certificate Invalid"));
4255 break;
4259 static void
4260 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4262 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4263 PurpleProxyConnectData *connect_data;
4265 sip->listen_data = NULL;
4267 sip->listenfd = listenfd;
4268 if (sip->listenfd == -1) {
4269 purple_connection_error(sip->gc, _("Could not create listen socket"));
4270 return;
4273 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4274 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4275 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4276 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4277 sipe_newconn_cb, sip->gc);
4278 purple_debug_info("sipe", "connecting to %s port %d\n",
4279 sip->realhostname, sip->realport);
4280 /* open tcp connection to the server */
4281 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4282 sip->realport, login_cb, sip->gc);
4284 if (connect_data == NULL) {
4285 purple_connection_error(sip->gc, _("Couldn't create socket"));
4290 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4292 PurpleAccount *account = sip->account;
4293 PurpleConnection *gc = sip->gc;
4295 if (purple_account_get_bool(account, "useport", FALSE)) {
4296 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4297 port = purple_account_get_int(account, "port", 0);
4298 } else {
4299 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4302 sip->realhostname = hostname;
4303 sip->realport = port;
4305 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4306 hostname, port);
4308 /* TODO: is there a good default grow size? */
4309 if (sip->transport != SIPE_TRANSPORT_UDP)
4310 sip->txbuf = purple_circ_buffer_new(0);
4312 if (sip->transport == SIPE_TRANSPORT_TLS) {
4313 /* SSL case */
4314 if (!purple_ssl_is_supported()) {
4315 gc->wants_to_die = TRUE;
4316 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4317 return;
4320 purple_debug_info("sipe", "using SSL\n");
4322 sip->gsc = purple_ssl_connect(account, hostname, port,
4323 login_cb_ssl, sipe_ssl_connect_failure, gc);
4324 if (sip->gsc == NULL) {
4325 purple_connection_error(gc, _("Could not create SSL context"));
4326 return;
4328 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4329 /* UDP case */
4330 purple_debug_info("sipe", "using UDP\n");
4332 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4333 if (sip->query_data == NULL) {
4334 purple_connection_error(gc, _("Could not resolve hostname"));
4336 } else {
4337 /* TCP case */
4338 purple_debug_info("sipe", "using TCP\n");
4339 /* create socket for incoming connections */
4340 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4341 sipe_tcp_connect_listen_cb, sip);
4342 if (sip->listen_data == NULL) {
4343 purple_connection_error(gc, _("Could not create listen socket"));
4344 return;
4349 /* Service list for autodection */
4350 static const struct sipe_service_data service_autodetect[] = {
4351 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4352 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4353 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4354 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4355 { NULL, NULL, 0 }
4358 /* Service list for SSL/TLS */
4359 static const struct sipe_service_data service_tls[] = {
4360 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4361 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4362 { NULL, NULL, 0 }
4365 /* Service list for TCP */
4366 static const struct sipe_service_data service_tcp[] = {
4367 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4368 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4369 { NULL, NULL, 0 }
4372 /* Service list for UDP */
4373 static const struct sipe_service_data service_udp[] = {
4374 { "sip", "udp", SIPE_TRANSPORT_UDP },
4375 { NULL, NULL, 0 }
4378 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4379 static void resolve_next_service(struct sipe_account_data *sip,
4380 const struct sipe_service_data *start)
4382 if (start) {
4383 sip->service_data = start;
4384 } else {
4385 sip->service_data++;
4386 if (sip->service_data->service == NULL) {
4387 gchar *hostname;
4388 /* Try connecting to the SIP hostname directly */
4389 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4390 if (sip->auto_transport) {
4391 // If SSL is supported, default to using it; OCS servers aren't configured
4392 // by default to accept TCP
4393 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4394 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4395 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4398 hostname = g_strdup(sip->sipdomain);
4399 create_connection(sip, hostname, 0);
4400 return;
4404 /* Try to resolve next service */
4405 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4406 sip->service_data->transport,
4407 sip->sipdomain,
4408 srvresolved, sip);
4411 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4413 struct sipe_account_data *sip = data;
4415 sip->srv_query_data = NULL;
4417 /* find the host to connect to */
4418 if (results) {
4419 gchar *hostname = g_strdup(resp->hostname);
4420 int port = resp->port;
4421 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4422 hostname, port);
4423 g_free(resp);
4425 sip->transport = sip->service_data->type;
4427 create_connection(sip, hostname, port);
4428 } else {
4429 resolve_next_service(sip, NULL);
4433 static void sipe_login(PurpleAccount *account)
4435 PurpleConnection *gc;
4436 struct sipe_account_data *sip;
4437 gchar **signinname_login, **userserver, **domain_user;
4438 const char *transport;
4440 const char *username = purple_account_get_username(account);
4441 gc = purple_account_get_connection(account);
4443 if (strpbrk(username, " \t\v\r\n") != NULL) {
4444 gc->wants_to_die = TRUE;
4445 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4446 return;
4449 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4450 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4451 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4452 sip->gc = gc;
4453 sip->account = account;
4454 sip->registerexpire = 900;
4455 sip->reregister_set = FALSE;
4456 sip->reauthenticate_set = FALSE;
4457 sip->subscribed = FALSE;
4458 sip->subscribed_buddies = FALSE;
4460 signinname_login = g_strsplit(username, ",", 2);
4462 userserver = g_strsplit(signinname_login[0], "@", 2);
4463 purple_connection_set_display_name(gc, userserver[0]);
4464 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4465 sip->sipdomain = g_strdup(userserver[1]);
4467 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4468 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4469 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4471 sip->password = g_strdup(purple_connection_get_password(gc));
4473 g_strfreev(userserver);
4474 g_strfreev(domain_user);
4475 g_strfreev(signinname_login);
4477 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4479 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4481 /* TODO: Set the status correctly. */
4482 sip->status = g_strdup("available");
4484 transport = purple_account_get_string(account, "transport", "auto");
4485 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4486 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4487 SIPE_TRANSPORT_UDP;
4489 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4490 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4491 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4492 } else if (strcmp(transport, "auto") == 0) {
4493 sip->auto_transport = TRUE;
4494 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4495 } else if (strcmp(transport, "tls") == 0) {
4496 resolve_next_service(sip, service_tls);
4497 } else if (strcmp(transport, "tcp") == 0) {
4498 resolve_next_service(sip, service_tcp);
4499 } else {
4500 resolve_next_service(sip, service_udp);
4504 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4506 connection_free_all(sip);
4508 if (sip->query_data != NULL)
4509 purple_dnsquery_destroy(sip->query_data);
4510 sip->query_data == NULL;
4512 if (sip->srv_query_data != NULL)
4513 purple_srv_cancel(sip->srv_query_data);
4514 sip->srv_query_data = NULL;
4516 if (sip->listen_data != NULL)
4517 purple_network_listen_cancel(sip->listen_data);
4518 sip->listen_data = NULL;
4520 if (sip->gsc != NULL)
4521 purple_ssl_close(sip->gsc);
4522 sip->gsc = NULL;
4524 sipe_auth_free(&sip->registrar);
4525 sipe_auth_free(&sip->proxy);
4527 if (sip->txbuf)
4528 purple_circ_buffer_destroy(sip->txbuf);
4529 sip->txbuf = NULL;
4531 g_free(sip->realhostname);
4532 sip->realhostname = NULL;
4534 if (sip->listenpa)
4535 purple_input_remove(sip->listenpa);
4536 sip->listenpa = 0;
4537 if (sip->tx_handler)
4538 purple_input_remove(sip->tx_handler);
4539 sip->tx_handler = 0;
4540 if (sip->resendtimeout)
4541 purple_timeout_remove(sip->resendtimeout);
4542 sip->resendtimeout = 0;
4543 if (sip->timeouts) {
4544 GSList *entry = sip->timeouts;
4545 while (entry) {
4546 struct scheduled_action *sched_action = entry->data;
4547 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4548 purple_timeout_remove(sched_action->timeout_handler);
4549 g_free(sched_action->payload);
4550 g_free(sched_action->name);
4551 g_free(sched_action);
4552 entry = entry->next;
4555 g_slist_free(sip->timeouts);
4557 if (sip->contact)
4558 g_free(sip->contact);
4559 sip->contact = NULL;
4560 if (sip->regcallid)
4561 g_free(sip->regcallid);
4562 sip->regcallid = NULL;
4564 sip->fd = -1;
4565 sip->processing_input = FALSE;
4568 static void sipe_close(PurpleConnection *gc)
4570 struct sipe_account_data *sip = gc->proto_data;
4572 if (sip) {
4573 /* leave all conversations */
4574 im_session_close_all(sip);
4576 /* unregister */
4577 do_register_exp(sip, 0);
4579 sipe_connection_cleanup(sip);
4580 g_free(sip->sipdomain);
4581 g_free(sip->username);
4582 g_free(sip->password);
4583 g_free(sip->authdomain);
4584 g_free(sip->authuser);
4586 g_free(gc->proto_data);
4587 gc->proto_data = NULL;
4590 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4592 PurpleAccount *acct = purple_connection_get_account(gc);
4593 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4594 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4595 if (conv == NULL)
4596 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4597 purple_conversation_present(conv);
4598 g_free(id);
4601 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4604 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4605 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4608 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4610 PurpleNotifySearchResults *results;
4611 PurpleNotifySearchColumn *column;
4612 xmlnode *searchResults;
4613 xmlnode *mrow;
4614 int match_count = 0;
4615 gboolean more = FALSE;
4616 gchar *secondary;
4618 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4619 if (!searchResults) {
4620 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4621 return FALSE;
4624 results = purple_notify_searchresults_new();
4626 if (results == NULL) {
4627 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4628 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4630 xmlnode_free(searchResults);
4631 return FALSE;
4634 column = purple_notify_searchresults_column_new(_("User Name"));
4635 purple_notify_searchresults_column_add(results, column);
4637 column = purple_notify_searchresults_column_new(_("Name"));
4638 purple_notify_searchresults_column_add(results, column);
4640 column = purple_notify_searchresults_column_new(_("Company"));
4641 purple_notify_searchresults_column_add(results, column);
4643 column = purple_notify_searchresults_column_new(_("Country"));
4644 purple_notify_searchresults_column_add(results, column);
4646 column = purple_notify_searchresults_column_new(_("Email"));
4647 purple_notify_searchresults_column_add(results, column);
4649 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4650 GList *row = NULL;
4652 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4653 row = g_list_append(row, g_strdup(uri_parts[1]));
4654 g_strfreev(uri_parts);
4656 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4657 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4658 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4659 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4661 purple_notify_searchresults_row_add(results, row);
4662 match_count++;
4665 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4666 char *data = xmlnode_get_data_unescaped(mrow);
4667 more = (g_strcasecmp(data, "true") == 0);
4668 g_free(data);
4671 secondary = g_strdup_printf(
4672 dngettext(GETTEXT_PACKAGE,
4673 "Found %d contact%s:",
4674 "Found %d contacts%s:", match_count),
4675 match_count, more ? _(" (more matched your query)") : "");
4677 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4678 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4679 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4681 g_free(secondary);
4682 xmlnode_free(searchResults);
4683 return TRUE;
4686 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4688 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4689 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4690 unsigned i = 0;
4692 do {
4693 PurpleRequestField *field = entries->data;
4694 const char *id = purple_request_field_get_id(field);
4695 const char *value = purple_request_field_string_get_value(field);
4697 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
4699 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
4700 } while ((entries = g_list_next(entries)) != NULL);
4701 attrs[i] = NULL;
4703 if (i > 0) {
4704 gchar *query = g_strjoinv(NULL, attrs);
4705 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
4706 send_soap_request_with_cb(gc->proto_data, body,
4707 (TransCallback) process_search_contact_response, NULL);
4708 g_free(body);
4709 g_free(query);
4712 g_strfreev(attrs);
4715 static void sipe_show_find_contact(PurplePluginAction *action)
4717 PurpleConnection *gc = (PurpleConnection *) action->context;
4718 PurpleRequestFields *fields;
4719 PurpleRequestFieldGroup *group;
4720 PurpleRequestField *field;
4722 fields = purple_request_fields_new();
4723 group = purple_request_field_group_new(NULL);
4724 purple_request_fields_add_group(fields, group);
4726 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4727 purple_request_field_group_add_field(group, field);
4728 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4729 purple_request_field_group_add_field(group, field);
4730 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4731 purple_request_field_group_add_field(group, field);
4732 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4733 purple_request_field_group_add_field(group, field);
4735 purple_request_fields(gc,
4736 _("Search"),
4737 _("Search for a Contact"),
4738 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4739 fields,
4740 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4741 _("_Cancel"), NULL,
4742 purple_connection_get_account(gc), NULL, NULL, gc);
4745 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4747 GList *menu = NULL;
4748 PurplePluginAction *act;
4750 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4751 menu = g_list_prepend(menu, act);
4753 menu = g_list_reverse(menu);
4755 return menu;
4758 static void dummy_permit_deny(PurpleConnection *gc)
4762 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4764 return TRUE;
4768 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4770 return TRUE;
4774 static void sipe_plugin_destroy(PurplePlugin *plugin)
4778 static char *sipe_status_text(PurpleBuddy *buddy)
4780 struct sipe_account_data *sip;
4781 struct sipe_buddy *sbuddy;
4782 char *text = NULL;
4784 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4785 if (sip) //happens on pidgin exit
4787 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4788 if (sbuddy && sbuddy->annotation)
4790 text = g_strdup(sbuddy->annotation);
4794 return text;
4797 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4799 const PurplePresence *presence = purple_buddy_get_presence(buddy);
4800 const PurpleStatus *status = purple_presence_get_active_status(presence);
4801 struct sipe_account_data *sip;
4802 struct sipe_buddy *sbuddy;
4803 char *annotation = NULL;
4805 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4806 if (sip) //happens on pidgin exit
4808 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4809 if (sbuddy)
4811 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
4815 //Layout
4816 if (purple_presence_is_online(presence))
4818 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
4821 if (annotation)
4823 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
4824 g_free(annotation);
4829 static GHashTable *
4830 sipe_get_account_text_table(PurpleAccount *account)
4832 GHashTable *table;
4833 table = g_hash_table_new(g_str_hash, g_str_equal);
4834 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
4835 return table;
4838 static PurpleBuddy *
4839 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
4841 PurpleBuddy *clone;
4842 const gchar *server_alias, *email;
4843 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
4845 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
4847 purple_blist_add_buddy(clone, NULL, group, NULL);
4849 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
4850 if (server_alias) {
4851 purple_blist_server_alias_buddy(clone, server_alias);
4854 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4855 if (email) {
4856 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
4859 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
4860 //for UI to update;
4861 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
4862 return clone;
4865 static void
4866 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
4868 PurpleBuddy *buddy, *b;
4869 PurpleConnection *gc;
4870 PurpleGroup * group = purple_find_group(group_name);
4872 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
4874 buddy = (PurpleBuddy *)node;
4876 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
4877 gc = purple_account_get_connection(buddy->account);
4879 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
4880 if (!b){
4881 b = purple_blist_add_buddy_clone(group, buddy);
4884 sipe_group_buddy(gc, buddy->name, NULL, group_name);
4887 static void
4888 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
4890 const gchar *email;
4891 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
4893 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4894 if (email)
4896 char *mailto = g_strdup_printf("mailto:%s", email);
4897 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
4898 #ifndef _WIN32
4900 pid_t pid;
4901 char *const parmList[] = {mailto, NULL};
4902 if ((pid = fork()) == -1)
4904 purple_debug_info("sipe", "fork() error\n");
4906 else if (pid == 0)
4908 execvp("xdg-email", parmList);
4909 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
4912 #else
4914 BOOL ret;
4915 _flushall();
4916 errno = 0;
4917 //@TODO resolve env variable %WINDIR% first
4918 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
4919 if (errno)
4921 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
4924 #endif
4926 g_free(mailto);
4928 else
4930 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
4935 * A menu which appear when right-clicking on buddy in contact list.
4937 static GList *
4938 sipe_buddy_menu(PurpleBuddy *buddy)
4940 PurpleBlistNode *g_node;
4941 PurpleGroup *group, *gr_parent;
4942 PurpleMenuAction *act;
4943 GList *menu = NULL;
4944 GList *menu_groups = NULL;
4946 act = purple_menu_action_new(_("Send Email..."),
4947 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
4948 NULL, NULL);
4949 menu = g_list_prepend(menu, act);
4951 gr_parent = purple_buddy_get_group(buddy);
4952 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
4953 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
4954 continue;
4956 group = (PurpleGroup *)g_node;
4957 if (group == gr_parent)
4958 continue;
4960 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
4961 continue;
4963 act = purple_menu_action_new(purple_group_get_name(group),
4964 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
4965 group->name, NULL);
4966 menu_groups = g_list_prepend(menu_groups, act);
4968 menu_groups = g_list_reverse(menu_groups);
4970 act = purple_menu_action_new(_("Copy to"),
4971 NULL,
4972 NULL, menu_groups);
4973 menu = g_list_prepend(menu, act);
4974 menu = g_list_reverse(menu);
4976 return menu;
4979 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
4980 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4981 return sipe_buddy_menu((PurpleBuddy *) node);
4982 } else {
4983 return NULL;
4987 static void sipe_get_info(PurpleConnection *gc, const char *username) {
4988 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
4989 PurpleBuddy *pbuddy;
4990 struct sipe_account_data *sip;
4991 struct sipe_buddy *sbuddy;
4992 const char *email;
4993 const char *device_name = NULL;
4994 const char *alias;
4995 const char *server_alias;
4997 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username,
4998 gc->account->username);
5000 pbuddy = purple_find_buddy(gc->account, username);
5001 alias = purple_buddy_get_local_alias(pbuddy);
5002 server_alias = purple_buddy_get_server_alias(pbuddy);
5004 sip = (struct sipe_account_data *)gc->proto_data;
5005 if (sip)
5007 sbuddy = g_hash_table_lookup(sip->buddies, username);
5008 if (sbuddy)
5010 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5014 //Layout
5015 if (server_alias)
5017 purple_notify_user_info_add_pair(info, _("Nickname"), server_alias);
5020 // same as server alias, do not present
5021 alias = alias && server_alias && !strcmp(alias, server_alias) ? NULL : alias;
5022 if (alias)
5024 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5027 email = purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email");
5028 if (email)
5030 purple_notify_user_info_add_pair(info, _("Email"), email);
5033 if (device_name)
5035 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5038 /* show a buddy's user info in a nice dialog box */
5039 purple_notify_userinfo(gc, /* connection the buddy info came through */
5040 username, /* buddy's username */
5041 info, /* body */
5042 NULL, /* callback called when dialog closed */
5043 NULL); /* userdata for callback */
5046 static PurplePlugin *my_protocol = NULL;
5048 static PurplePluginProtocolInfo prpl_info =
5051 NULL, /* user_splits */
5052 NULL, /* protocol_options */
5053 NO_BUDDY_ICONS, /* icon_spec */
5054 sipe_list_icon, /* list_icon */
5055 NULL, /* list_emblems */
5056 sipe_status_text, /* status_text */
5057 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5058 sipe_status_types, /* away_states */
5059 sipe_blist_node_menu, /* blist_node_menu */
5060 NULL, /* chat_info */
5061 NULL, /* chat_info_defaults */
5062 sipe_login, /* login */
5063 sipe_close, /* close */
5064 sipe_im_send, /* send_im */
5065 NULL, /* set_info */ // TODO maybe
5066 sipe_send_typing, /* send_typing */
5067 sipe_get_info, /* get_info */
5068 sipe_set_status, /* set_status */
5069 NULL, /* set_idle */
5070 NULL, /* change_passwd */
5071 sipe_add_buddy, /* add_buddy */
5072 NULL, /* add_buddies */
5073 sipe_remove_buddy, /* remove_buddy */
5074 NULL, /* remove_buddies */
5075 sipe_add_permit, /* add_permit */
5076 sipe_add_deny, /* add_deny */
5077 sipe_add_deny, /* rem_permit */
5078 sipe_add_permit, /* rem_deny */
5079 dummy_permit_deny, /* set_permit_deny */
5080 NULL, /* join_chat */
5081 NULL, /* reject_chat */
5082 NULL, /* get_chat_name */
5083 NULL, /* chat_invite */
5084 NULL, /* chat_leave */
5085 NULL, /* chat_whisper */
5086 NULL, /* chat_send */
5087 sipe_keep_alive, /* keepalive */
5088 NULL, /* register_user */
5089 NULL, /* get_cb_info */ // deprecated
5090 NULL, /* get_cb_away */ // deprecated
5091 sipe_alias_buddy, /* alias_buddy */
5092 sipe_group_buddy, /* group_buddy */
5093 sipe_rename_group, /* rename_group */
5094 NULL, /* buddy_free */
5095 sipe_convo_closed, /* convo_closed */
5096 purple_normalize_nocase, /* normalize */
5097 NULL, /* set_buddy_icon */
5098 sipe_remove_group, /* remove_group */
5099 NULL, /* get_cb_real_name */ // TODO?
5100 NULL, /* set_chat_topic */
5101 NULL, /* find_blist_chat */
5102 NULL, /* roomlist_get_list */
5103 NULL, /* roomlist_cancel */
5104 NULL, /* roomlist_expand_category */
5105 NULL, /* can_receive_file */
5106 NULL, /* send_file */
5107 NULL, /* new_xfer */
5108 NULL, /* offline_message */
5109 NULL, /* whiteboard_prpl_ops */
5110 sipe_send_raw, /* send_raw */
5111 NULL, /* roomlist_room_serialize */
5112 NULL, /* unregister_user */
5113 NULL, /* send_attention */
5114 NULL, /* get_attention_types */
5116 sizeof(PurplePluginProtocolInfo), /* struct_size */
5117 sipe_get_account_text_table, /* get_account_text_table */
5121 static PurplePluginInfo info = {
5122 PURPLE_PLUGIN_MAGIC,
5123 PURPLE_MAJOR_VERSION,
5124 PURPLE_MINOR_VERSION,
5125 PURPLE_PLUGIN_PROTOCOL, /**< type */
5126 NULL, /**< ui_requirement */
5127 0, /**< flags */
5128 NULL, /**< dependencies */
5129 PURPLE_PRIORITY_DEFAULT, /**< priority */
5130 "prpl-sipe", /**< id */
5131 "Microsoft LCS/OCS", /**< name */
5132 VERSION, /**< version */
5133 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5134 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5135 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5136 "Gabriel Burt <gburt@novell.com>", /**< author */
5137 PURPLE_WEBSITE, /**< homepage */
5138 sipe_plugin_load, /**< load */
5139 sipe_plugin_unload, /**< unload */
5140 sipe_plugin_destroy, /**< destroy */
5141 NULL, /**< ui_info */
5142 &prpl_info, /**< extra_info */
5143 NULL,
5144 sipe_actions,
5145 NULL,
5146 NULL,
5147 NULL,
5148 NULL
5151 static void init_plugin(PurplePlugin *plugin)
5153 PurpleAccountUserSplit *split;
5154 PurpleAccountOption *option;
5156 #ifdef ENABLE_NLS
5157 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5158 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5159 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5160 #endif
5162 purple_plugin_register(plugin);
5164 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5165 purple_account_user_split_set_reverse(split, FALSE);
5166 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5168 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5169 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5170 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5171 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5173 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5174 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5175 // Translators: noun (networking port)
5176 option = purple_account_option_int_new(_("Port"), "port", 5061);
5177 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5179 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5180 purple_account_option_add_list_item(option, _("Auto"), "auto");
5181 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5182 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5183 purple_account_option_add_list_item(option, _("UDP"), "udp");
5184 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5186 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5187 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5189 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5190 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5192 // TODO commented out so won't show in the preferences until we fix krb message signing
5193 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5194 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5196 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5197 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5198 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5201 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5202 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5203 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5204 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5205 my_protocol = plugin;
5208 /* I had to redefined the function for it load, but works */
5209 gboolean purple_init_plugin(PurplePlugin *plugin){
5210 plugin->info = &(info);
5211 init_plugin((plugin));
5212 sipe_plugin_load((plugin));
5213 return purple_plugin_register(plugin);