Another round of hunting down memory leaks & access-after-freed errors
[siplcs.git] / src / sipe.c
blob3914b5c69c95d410dfe4ab937cb3c1f6559f90ef
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2007 Anibal Avelar <avelar@gmail.com>
8 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
10 * ***
11 * Thanks to Google's Summer of Code Program and the helpful mentors
12 * ***
14 * Session-based SIP MESSAGE documentation:
15 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 #ifndef _WIN32
33 #include <sys/socket.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
36 #include <netinet/in.h>
37 #include <net/if.h>
38 #ifdef ENABLE_NLS
39 # include <libintl.h>
40 # define _(String) ((const char *) gettext (String))
41 #else
42 # define _(String) ((const char *) (String))
43 #endif /* ENABLE_NLS */
44 #else
45 #ifdef _DLL
46 #define _WS2TCPIP_H_
47 #define _WINSOCK2API_
48 #define _LIBC_INTERNAL_
49 #endif /* _DLL */
51 #include "internal.h"
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <glib.h>
61 #include "accountopt.h"
62 #include "blist.h"
63 #include "conversation.h"
64 #include "dnsquery.h"
65 #include "debug.h"
66 #include "notify.h"
67 #include "privacy.h"
68 #include "prpl.h"
69 #include "plugin.h"
70 #include "util.h"
71 #include "version.h"
72 #include "network.h"
73 #include "xmlnode.h"
74 #include "mime.h"
76 #include "sipe.h"
77 #include "sip-ntlm.h"
78 #ifdef USE_KERBEROS
79 #include "sipkrb5.h"
80 #endif /*USE_KERBEROS*/
82 #include "sipmsg.h"
83 #include "sipe-sign.h"
84 #include "dnssrv.h"
85 #include "request.h"
87 /* Keep in sync with sipe_transport_type! */
88 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
89 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
91 static char *gentag()
93 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
96 static gchar *get_epid()
98 return sipe_uuid_get_macaddr();
101 static char *genbranch()
103 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
104 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
105 rand() & 0xFFFF, rand() & 0xFFFF);
108 static char *gencallid()
110 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
111 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
112 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
113 rand() & 0xFFFF, rand() & 0xFFFF);
116 static gchar *find_tag(const gchar *hdr)
118 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
119 if (!tag) {
120 // In case it's at the end and there's no trailing ;
121 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
123 return tag;
127 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
129 return "sipe";
132 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
134 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
135 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
136 gpointer data);
138 static void sipe_close(PurpleConnection *gc);
140 static void sipe_subscribe_to_name_single(struct sipe_account_data *sip, const char * buddy_name);
141 static void sipe_subscribe_to_name_batched(struct sipe_account_data *sip, const char * buddy_name);
142 static void send_presence_info(struct sipe_account_data *sip);
144 static void sendout_pkt(PurpleConnection *gc, const char *buf);
146 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
148 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
149 if (timeout != NULL) {
150 sscanf(timeout, "%u", &sip->keepalive_timeout);
151 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
152 sip->keepalive_timeout);
156 static void sipe_keep_alive(PurpleConnection *gc)
158 struct sipe_account_data *sip = gc->proto_data;
159 if (sip->transport == SIPE_TRANSPORT_UDP) {
160 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
161 gchar buf[2] = {0, 0};
162 purple_debug_info("sipe", "sending keep alive\n");
163 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
164 } else {
165 time_t now = time(NULL);
166 if ((sip->keepalive_timeout > 0) &&
167 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
168 #if PURPLE_VERSION_CHECK(2,4,0)
169 && ((now - gc->last_received) >= sip->keepalive_timeout)
170 #endif
172 purple_debug_info("sipe", "sending keep alive\n");
173 sendout_pkt(gc, "\r\n\r\n");
174 sip->last_keepalive = now;
179 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
181 struct sip_connection *ret = NULL;
182 GSList *entry = sip->openconns;
183 while (entry) {
184 ret = entry->data;
185 if (ret->fd == fd) return ret;
186 entry = entry->next;
188 return NULL;
191 static void sipe_auth_free(struct sip_auth *auth)
193 g_free(auth->nonce);
194 auth->nonce = NULL;
195 g_free(auth->opaque);
196 auth->opaque = NULL;
197 g_free(auth->realm);
198 auth->realm = NULL;
199 g_free(auth->target);
200 auth->target = NULL;
201 g_free(auth->digest_session_key);
202 auth->digest_session_key = NULL;
203 g_free(auth->ntlm_key);
204 auth->ntlm_key = NULL;
205 auth->type = AUTH_TYPE_UNSET;
206 auth->retries = 0;
207 auth->expires = 0;
210 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
212 struct sip_connection *ret = g_new0(struct sip_connection, 1);
213 ret->fd = fd;
214 sip->openconns = g_slist_append(sip->openconns, ret);
215 return ret;
218 static void connection_remove(struct sipe_account_data *sip, int fd)
220 struct sip_connection *conn = connection_find(sip, fd);
221 if (conn) {
222 sip->openconns = g_slist_remove(sip->openconns, conn);
223 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
224 g_free(conn->inbuf);
225 g_free(conn);
229 static void connection_free_all(struct sipe_account_data *sip)
231 struct sip_connection *ret = NULL;
232 GSList *entry = sip->openconns;
233 while (entry) {
234 ret = entry->data;
235 connection_remove(sip, ret->fd);
236 entry = sip->openconns;
240 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
242 const gchar *method = msg->method;
243 const gchar *target = msg->target;
244 gchar noncecount[9];
245 gchar *response;
246 gchar *ret;
247 gchar *tmp = NULL;
248 const char *authdomain = 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
1495 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1499 static void sipe_subscribe_to_name_batched(struct sipe_account_data *sip, const char * buddy_name){
1500 gchar *resource_uri = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1501 gchar *to = g_strdup_printf("sip:%s", sip->username);
1502 gchar *tmp = get_contact(sip);
1503 gchar *request;
1504 gchar *content;
1505 request = g_strdup_printf(
1506 // 2005 fix. We probably should not require a feature as we are backward compatible and support either response.
1507 // But we declare (in Supported: eventlist) that we are able to recieve such messages.
1508 //"Require: adhoclist, categoryList\r\n"
1509 "Supported: eventlist\r\n"
1510 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1511 "Supported: ms-piggyback-first-notify\r\n"
1512 "Supported: com.microsoft.autoextend\r\n"
1513 "Supported: ms-benotify\r\n"
1514 "Proxy-Require: ms-benotify\r\n"
1515 "Event: presence\r\n"
1516 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1517 "Contact: %s\r\n", tmp);
1519 content = g_strdup_printf(
1520 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1521 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1522 "<resource uri=\"%s\"/>\n"
1523 "</adhocList>\n"
1524 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1525 "<category name=\"note\"/>\n"
1526 "<category name=\"state\"/>\n"
1527 "</categoryList>\n"
1528 "</action>\n"
1529 "</batchSub>", sip->username, resource_uri
1532 g_free(tmp);
1534 /* subscribe to buddy presence */
1535 send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1537 g_free(content);
1538 g_free(to);
1539 g_free(request);
1540 g_free(resource_uri);
1544 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1545 * The user sends a single SUBSCRIBE request to the subscribed contact.
1546 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1550 static void sipe_subscribe_to_name_single(struct sipe_account_data *sip, const char * buddy_name)
1552 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1553 gchar *tmp = get_contact(sip);
1554 gchar *request;
1555 gchar *content;
1556 request = g_strdup_printf(
1557 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1558 "Supported: ms-piggyback-first-notify\r\n"
1559 "Supported: com.microsoft.autoextend\r\n"
1560 "Supported: ms-benotify\r\n"
1561 "Proxy-Require: ms-benotify\r\n"
1562 "Event: presence\r\n"
1563 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1564 "Contact: %s\r\n", tmp);
1566 content = g_strdup_printf(
1567 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1568 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1569 "<resource uri=\"%s\"/>\n"
1570 "</adhocList>\n"
1571 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1572 "<category name=\"note\"/>\n"
1573 "<category name=\"state\"/>\n"
1574 "</categoryList>\n"
1575 "</action>\n"
1576 "</batchSub>", sip->username, to
1579 g_free(tmp);
1581 /* subscribe to buddy presence */
1582 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1584 g_free(content);
1585 g_free(to);
1586 g_free(request);
1589 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1591 const char *status_id = purple_status_get_id(status);
1592 struct sipe_account_data *sip = NULL;
1594 if (!purple_status_is_active(status))
1595 return;
1597 if (account->gc)
1598 sip = account->gc->proto_data;
1600 if (sip) {
1601 g_free(sip->status);
1602 sip->status = g_strdup(status_id);
1603 send_presence_info(sip);
1607 static void
1608 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1610 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1611 sipe_group_set_user(sip, name);
1614 static void
1615 sipe_group_buddy(PurpleConnection *gc,
1616 const char *who,
1617 const char *old_group_name,
1618 const char *new_group_name)
1620 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1621 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1622 struct sipe_group * old_group = NULL;
1623 struct sipe_group * new_group;
1625 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1626 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1628 if(!buddy) { // buddy not in roaming list
1629 return;
1632 if (old_group_name) {
1633 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1635 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1637 if (old_group) {
1638 buddy->groups = g_slist_remove(buddy->groups, old_group);
1639 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1642 if (!new_group) {
1643 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1644 } else {
1645 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1646 sipe_group_set_user(sip, who);
1650 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1652 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1653 struct sipe_buddy *b;
1655 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1657 // Prepend sip: if needed
1658 if (strncmp("sip:", buddy->name, 4)) {
1659 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1660 purple_blist_rename_buddy(buddy, buf);
1661 g_free(buf);
1664 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1665 b = g_new0(struct sipe_buddy, 1);
1666 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1667 b->name = g_strdup(buddy->name);
1668 g_hash_table_insert(sip->buddies, b->name, b);
1669 sipe_group_buddy(gc, b->name, NULL, group->name);
1670 sipe_subscribe_to_name_batched(sip, b->name); //@TODO should go to callback
1671 } else {
1672 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1677 * Unassociates buddy from group first.
1678 * Then see if no groups left, removes buddy completely.
1679 * Otherwise updates buddy groups on server.
1681 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1683 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1684 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1685 struct sipe_group *g = NULL;
1687 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1689 if (!b) return;
1691 if (group) {
1692 g = sipe_group_find_by_name(sip, group->name);
1695 if (g) {
1696 b->groups = g_slist_remove(b->groups, g);
1697 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1700 if (g_slist_length(b->groups) < 1) {
1701 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1702 sipe_cancel_scheduled_action(sip, action_name);
1703 g_free(action_name);
1705 g_hash_table_remove(sip->buddies, buddy->name);
1707 if (b->name) {
1708 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1709 send_soap_request(sip, body);
1710 g_free(body);
1713 g_free(b->name);
1714 g_free(b->annotation);
1715 g_free(b->device_name);
1716 g_slist_free(b->groups);
1717 g_free(b);
1718 } else {
1719 //updates groups on server
1720 sipe_group_set_user(sip, b->name);
1725 static void
1726 sipe_rename_group(PurpleConnection *gc,
1727 const char *old_name,
1728 PurpleGroup *group,
1729 GList *moved_buddies)
1731 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1732 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1733 if (group) {
1734 sipe_group_rename(sip, s_group, group->name);
1735 } else {
1736 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1740 static void
1741 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
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, group->name);
1745 if (s_group) {
1746 gchar *body;
1747 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1748 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1749 send_soap_request(sip, body);
1750 g_free(body);
1752 sip->groups = g_slist_remove(sip->groups, s_group);
1753 g_free(s_group->name);
1754 } else {
1755 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1759 static GList *sipe_status_types(PurpleAccount *acc)
1761 PurpleStatusType *type;
1762 GList *types = NULL;
1764 // Online
1765 type = purple_status_type_new_with_attrs(
1766 PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE,
1767 // Translators: noun
1768 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1769 NULL);
1770 types = g_list_append(types, type);
1772 // Busy
1773 type = purple_status_type_new_with_attrs(
1774 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1775 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1776 NULL);
1777 types = g_list_append(types, type);
1779 // Do Not Disturb (Not let user set it)
1780 type = purple_status_type_new_with_attrs(
1781 PURPLE_STATUS_UNAVAILABLE, "do-not-disturb", "Do Not Disturb", TRUE, FALSE, FALSE,
1782 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1783 NULL);
1784 types = g_list_append(types, type);
1786 // Be Right Back
1787 type = purple_status_type_new_with_attrs(
1788 PURPLE_STATUS_AWAY, "be-right-back", _("Be Right Back"), TRUE, TRUE, FALSE,
1789 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1790 NULL);
1791 types = g_list_append(types, type);
1793 // Away
1794 type = purple_status_type_new_with_attrs(
1795 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1796 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1797 NULL);
1798 types = g_list_append(types, type);
1800 //On The Phone
1801 type = purple_status_type_new_with_attrs(
1802 PURPLE_STATUS_UNAVAILABLE, "on-the-phone", _("On The Phone"), TRUE, TRUE, FALSE,
1803 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1804 NULL);
1805 types = g_list_append(types, type);
1807 //Out To Lunch
1808 type = purple_status_type_new_with_attrs(
1809 PURPLE_STATUS_AWAY, "out-to-lunch", "Out To Lunch", TRUE, TRUE, FALSE,
1810 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1811 NULL);
1812 types = g_list_append(types, type);
1814 //Appear Offline
1815 type = purple_status_type_new_full(
1816 PURPLE_STATUS_INVISIBLE, NULL, "Appear Offline", TRUE, TRUE, FALSE);
1817 types = g_list_append(types, type);
1819 // Offline
1820 type = purple_status_type_new_full(
1821 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1822 types = g_list_append(types, type);
1824 return types;
1828 * A callback for g_hash_table_foreach
1830 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1832 sipe_subscribe_to_name_batched(sip, buddy->name);
1836 * Removes entries from purple buddy list
1837 * that does not correspond ones in the roaming contact list.
1839 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1840 GSList *buddies = purple_find_buddies(sip->account, NULL);
1841 GSList *entry = buddies;
1842 struct sipe_buddy *buddy;
1843 PurpleBuddy *b;
1844 PurpleGroup *g;
1846 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1847 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1848 while (entry) {
1849 b = entry->data;
1850 g = purple_buddy_get_group(b);
1851 buddy = g_hash_table_lookup(sip->buddies, b->name);
1852 if(buddy) {
1853 gboolean in_sipe_groups = FALSE;
1854 GSList *entry2 = buddy->groups;
1855 while (entry2) {
1856 struct sipe_group *group = entry2->data;
1857 if (!strcmp(group->name, g->name)) {
1858 in_sipe_groups = TRUE;
1859 break;
1861 entry2 = entry2->next;
1863 if(!in_sipe_groups) {
1864 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1865 purple_blist_remove_buddy(b);
1867 } else {
1868 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1869 purple_blist_remove_buddy(b);
1871 entry = entry->next;
1875 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1877 int len = msg->bodylen;
1879 gchar *tmp = sipmsg_find_header(msg, "Event");
1880 xmlnode *item;
1881 xmlnode *isc;
1882 gchar *contacts_delta;
1883 xmlnode *group_node;
1884 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1885 return FALSE;
1888 purple_debug_info("sipe", "msg->body:%s\n", msg->body);
1890 /* Convert the contact from XML to Purple Buddies */
1891 isc = xmlnode_from_str(msg->body, len);
1892 if (!isc) {
1893 return FALSE;
1896 contacts_delta = g_strdup(xmlnode_get_attrib(isc, "deltaNum"));
1897 if (contacts_delta) {
1898 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1901 /* Parse groups */
1902 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1903 struct sipe_group * group = g_new0(struct sipe_group, 1);
1905 group->name = g_strdup(xmlnode_get_attrib(group_node, "name"));
1906 if (!strncmp(group->name, "~", 1)){
1907 // TODO translate
1908 group->name = "Other Contacts";
1910 group->name = g_strdup(group->name);
1911 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1913 sipe_group_add(sip, group);
1916 // Make sure we have at least one group
1917 if (g_slist_length(sip->groups) == 0) {
1918 struct sipe_group * group = g_new0(struct sipe_group, 1);
1919 PurpleGroup *purple_group;
1920 // TODO translate
1921 group->name = g_strdup("Other Contacts");
1922 group->id = 1;
1923 purple_group = purple_group_new(group->name);
1924 purple_blist_add_group(purple_group, NULL);
1925 sip->groups = g_slist_append(sip->groups, group);
1928 /* Parse contacts */
1929 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1930 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1931 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1932 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1933 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1934 gchar **item_groups;
1935 struct sipe_group *group = NULL;
1936 struct sipe_buddy *buddy = NULL;
1937 int i = 0;
1939 // assign to group Other Contacts if nothing else received
1940 if(!groups || !strcmp("", groups) ) {
1941 group = sipe_group_find_by_name(sip, "Other Contacts");
1942 groups = group ? g_strdup_printf("%d", group->id) : "1";
1945 item_groups = g_strsplit(groups, " ", 0);
1947 while (item_groups[i]) {
1948 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1950 // If couldn't find the right group for this contact, just put them in the first group we have
1951 if (group == NULL && g_slist_length(sip->groups) > 0) {
1952 group = sip->groups->data;
1955 if (group != NULL) {
1956 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1957 if (!b){
1958 b = purple_buddy_new(sip->account, buddy_name, uri);
1959 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1962 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1963 if (name != NULL && strlen(name) != 0) {
1964 purple_blist_alias_buddy(b, name);
1968 if (!buddy) {
1969 buddy = g_new0(struct sipe_buddy, 1);
1970 buddy->name = g_strdup(b->name);
1971 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1974 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1976 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
1977 } else {
1978 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1979 name);
1982 i++;
1983 } // while, contact groups
1984 g_strfreev(item_groups);
1985 g_free(groups);
1986 g_free(name);
1987 g_free(buddy_name);
1988 g_free(uri);
1990 } // for, contacts
1992 xmlnode_free(isc);
1994 sipe_cleanup_local_blist(sip);
1996 //subscribe to buddies
1997 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1998 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
1999 sip->subscribed_buddies = TRUE;
2002 return 0;
2008 * Subscribe roaming contacts
2010 static void sipe_subscribe_buddylist(struct sipe_account_data *sip,struct sipmsg *msg)
2012 gchar *to = g_strdup_printf("sip:%s", sip->username);
2013 gchar *tmp = get_contact(sip);
2014 gchar *hdr = g_strdup_printf(
2015 "Event: vnd-microsoft-roaming-contacts\r\n"
2016 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2017 "Supported: com.microsoft.autoextend\r\n"
2018 "Supported: ms-benotify\r\n"
2019 "Proxy-Require: ms-benotify\r\n"
2020 "Supported: ms-piggyback-first-notify\r\n"
2021 "Contact: %s\r\n", tmp);
2022 g_free(tmp);
2024 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_roaming_contacts);
2025 g_free(to);
2026 g_free(hdr);
2029 static gboolean
2030 sipe_process_pending_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2032 sipe_process_incoming_pending (sip, msg);
2033 return TRUE;
2036 static void sipe_subscribe_pending_buddies(struct sipe_account_data *sip,struct sipmsg *msg)
2038 gchar *to = g_strdup_printf("sip:%s", sip->username);
2039 gchar *tmp = get_contact(sip);
2040 gchar *hdr = g_strdup_printf(
2041 "Event: presence.wpending\r\n"
2042 "Accept: text/xml+msrtc.wpending\r\n"
2043 "Supported: com.microsoft.autoextend\r\n"
2044 "Supported: ms-benotify\r\n"
2045 "Proxy-Require: ms-benotify\r\n"
2046 "Supported: ms-piggyback-first-notify\r\n"
2047 "Contact: %s\r\n", tmp);
2048 g_free(tmp);
2050 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_pending_response);
2051 g_free(to);
2052 g_free(hdr);
2055 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2057 const gchar *contacts_delta;
2058 xmlnode *xml;
2060 xml = xmlnode_from_str(msg->body, msg->bodylen);
2061 if (!xml)
2063 return;
2066 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2067 if (contacts_delta)
2069 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2072 xmlnode_free(xml);
2075 static gboolean
2076 sipe_process_acl_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2078 sipe_process_roaming_acl(sip, msg);
2079 return TRUE;
2083 * When we receive some self (BE) NOTIFY with a new subscriber
2084 * we sends a setSubscribers request to him [SIP-PRES]
2088 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2090 gchar *to;
2091 gchar *tmp;
2092 xmlnode *xml;
2093 xmlnode *node;
2094 const char *user;
2095 gchar *hdr;
2096 gchar *body;
2098 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2100 xml = xmlnode_from_str(msg->body, msg->bodylen);
2101 if (!xml) return;
2103 node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL);
2104 if (!node) {
2105 xmlnode_free(xml);
2106 return;
2109 user = xmlnode_get_attrib(node, "user");
2110 if (!user) {
2111 xmlnode_free(xml);
2112 return;
2115 to = g_strdup_printf("sip:%s", sip->username);
2116 tmp = get_contact(sip);
2117 hdr = g_strdup_printf(
2118 "Contact: %s\r\n"
2119 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", tmp);
2121 body = g_strdup_printf(
2122 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2123 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2124 "</setSubscribers>",user);
2126 g_free(tmp);
2127 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2128 g_free(body);
2129 g_free(to);
2130 g_free(hdr);
2131 xmlnode_free(node);
2134 static void sipe_subscribe_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2136 gchar *to = g_strdup_printf("sip:%s", sip->username);
2137 gchar *tmp = get_contact(sip);
2138 gchar *hdr = g_strdup_printf(
2139 "Event: vnd-microsoft-roaming-ACL\r\n"
2140 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2141 "Supported: com.microsoft.autoextend\r\n"
2142 "Supported: ms-benotify\r\n"
2143 "Proxy-Require: ms-benotify\r\n"
2144 "Supported: ms-piggyback-first-notify\r\n"
2145 "Contact: %s\r\n", tmp);
2146 g_free(tmp);
2148 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_acl_response);
2149 g_free(to);
2150 g_free(hdr);
2154 * To request for presence information about the user, access level settings that have already been configured by the user
2155 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2156 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2159 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2161 gchar *to = g_strdup_printf("sip:%s", sip->username);
2162 gchar *tmp = get_contact(sip);
2163 gchar *hdr = g_strdup_printf(
2164 "Event: vnd-microsoft-roaming-self\r\n"
2165 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2166 "Supported: com.microsoft.autoextend\r\n"
2167 "Supported: ms-benotify\r\n"
2168 "Proxy-Require: ms-benotify\r\n"
2169 "Supported: ms-piggyback-first-notify\r\n"
2170 "Contact: %s\r\n"
2171 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2173 gchar *body=g_strdup(
2174 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2175 "<roaming type=\"categories\"/>"
2176 "<roaming type=\"containers\"/>"
2177 "<roaming type=\"subscribers\"/></roamingList>");
2179 g_free(tmp);
2180 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
2181 g_free(body);
2182 g_free(to);
2183 g_free(hdr);
2186 /** Subscription for provisioning information to help with initial
2187 * configuration. This subscription is a one-time query (denoted by the Expires header,
2188 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2189 * configuration, meeting policies, and policy settings that Communicator must enforce.
2190 * TODO: for what we need this information.
2193 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2195 gchar *to = g_strdup_printf("sip:%s", sip->username);
2196 gchar *tmp = get_contact(sip);
2197 gchar *hdr = g_strdup_printf(
2198 "Event: vnd-microsoft-provisioning-v2\r\n"
2199 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2200 "Supported: com.microsoft.autoextend\r\n"
2201 "Supported: ms-benotify\r\n"
2202 "Proxy-Require: ms-benotify\r\n"
2203 "Supported: ms-piggyback-first-notify\r\n"
2204 "Expires: 0\r\n"
2205 "Contact: %s\r\n"
2206 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2207 gchar *body = g_strdup(
2208 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2209 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2210 "<provisioningGroup name=\"ucPolicy\"/>"
2211 "</provisioningGroupList>");
2213 g_free(tmp);
2214 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
2215 g_free(body);
2216 g_free(to);
2217 g_free(hdr);
2220 /* IM Session (INVITE and MESSAGE methods) */
2222 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2224 struct sip_im_session *session;
2225 GSList *entry;
2226 if (sip == NULL || who == NULL) {
2227 return NULL;
2230 entry = sip->im_sessions;
2231 while (entry) {
2232 session = entry->data;
2233 if ((who != NULL && !strcmp(who, session->with))) {
2234 return session;
2236 entry = entry->next;
2238 return NULL;
2241 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2243 struct sip_im_session *session = find_im_session(sip, who);
2244 if (!session) {
2245 session = g_new0(struct sip_im_session, 1);
2246 session->with = g_strdup(who);
2247 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2249 return session;
2252 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2254 struct sip_dialog *dialog = session->dialog;
2255 GSList *entry;
2257 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2259 if (dialog) {
2260 entry = dialog->routes;
2261 while (entry) {
2262 g_free(entry->data);
2263 entry = g_slist_remove(entry, entry->data);
2265 entry = dialog->supported;
2266 while (entry) {
2267 g_free(entry->data);
2268 entry = g_slist_remove(entry, entry->data);
2270 g_free(dialog->callid);
2271 g_free(dialog->ourtag);
2272 g_free(dialog->theirtag);
2273 g_free(dialog->theirepid);
2274 g_free(dialog->request);
2276 g_free(session->dialog);
2278 entry = session->outgoing_message_queue;
2279 while (entry) {
2280 g_free(entry->data);
2281 entry = g_slist_remove(entry, entry->data);
2284 g_free(session->with);
2285 g_free(session);
2288 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2290 char *msg, *msg_tmp;
2291 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2292 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2293 g_free(msg_tmp);
2294 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2295 "possibly because one or more persons are offline:\n%s") ,
2296 msg ? msg : "");
2297 purple_conv_present_error(with, sip->account, msg_tmp);
2298 g_free(msg);
2299 g_free(msg_tmp);
2302 static void sipe_im_remove_first_from_queue (struct sip_im_session * session);
2303 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2305 static gboolean
2306 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2308 gboolean ret = TRUE;
2309 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2310 struct sip_im_session * session = find_im_session(sip, with);
2311 struct sip_dialog *dialog;
2313 if (!session) {
2314 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2315 g_free(with);
2316 return FALSE;
2319 if (msg->response != 200) {
2320 gchar *queued_msg = NULL;
2321 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2323 if (session->outgoing_message_queue) {
2324 queued_msg = session->outgoing_message_queue->data;
2326 sipe_present_message_undelivered_err(with, sip, queued_msg);
2327 im_session_destroy(sip, session);
2328 g_free(with);
2329 return FALSE;
2332 dialog = session->dialog;
2333 if (!dialog) {
2334 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2335 ret = FALSE;
2338 sipe_im_remove_first_from_queue(session);
2339 sipe_im_process_queue(sip, session);
2340 g_free(with);
2341 return ret;
2344 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2346 gchar *hdr;
2347 gchar *fullto;
2348 gchar *tmp;
2349 char *msgformat;
2350 char *msgtext;
2351 gchar *msgr_value;
2352 gchar *msgr;
2354 if (strncmp("sip:", session->with, 4)) {
2355 fullto = g_strdup_printf("sip:%s", session->with);
2356 } else {
2357 fullto = g_strdup(session->with);
2360 sipe_parse_html(msg, &msgformat, &msgtext);
2361 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2363 msgr_value = sipmsg_get_msgr_string(msgformat);
2364 g_free(msgformat);
2365 if (msgr_value) {
2366 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2367 g_free(msgr_value);
2368 } else {
2369 msgr = g_strdup("");
2372 tmp = get_contact(sip);
2373 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2374 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2375 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2376 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2377 tmp, msgr);
2378 g_free(tmp);
2379 g_free(msgr);
2381 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2382 g_free(msgtext);
2383 g_free(hdr);
2384 g_free(fullto);
2388 static void
2389 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2391 GSList *entry = session->outgoing_message_queue;
2392 if (entry) {
2393 char *queued_msg = entry->data;
2394 sipe_send_message(sip, session, queued_msg);
2398 static void
2399 sipe_im_remove_first_from_queue (struct sip_im_session * session)
2401 if (session && session->outgoing_message_queue) {
2402 char *queued_msg = session->outgoing_message_queue->data;
2403 // Remove from the queue and free the string
2404 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2405 g_free(queued_msg);
2409 static void
2410 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2412 GSList *hdr = msg->headers;
2413 struct siphdrelement *elem;
2414 gchar *contact;
2416 while(hdr)
2418 elem = hdr->data;
2419 if(!strcmp(elem->name, "Record-Route"))
2421 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2422 dialog->routes = g_slist_append(dialog->routes, route);
2424 hdr = g_slist_next(hdr);
2427 if (outgoing)
2429 dialog->routes = g_slist_reverse(dialog->routes);
2432 if (dialog->routes)
2434 dialog->request = dialog->routes->data;
2435 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2438 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2439 dialog->routes = g_slist_append(dialog->routes, contact);
2442 static void
2443 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2445 GSList *hdr = msg->headers;
2446 struct siphdrelement *elem;
2447 while(hdr)
2449 elem = hdr->data;
2450 if(!strcmp(elem->name, "Supported")
2451 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2453 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2456 hdr = g_slist_next(hdr);
2460 static void
2461 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2463 gchar *us = outgoing ? "From" : "To";
2464 gchar *them = outgoing ? "To" : "From";
2466 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2467 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2468 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2469 if (!dialog->theirepid) {
2470 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2472 if (!dialog->theirepid) {
2473 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2476 sipe_get_route_header(msg, dialog, outgoing);
2477 sipe_get_supported_header(msg, dialog, outgoing);
2481 static gboolean
2482 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2484 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2485 struct sip_im_session * session = find_im_session(sip, with);
2486 struct sip_dialog *dialog;
2488 if (!session) {
2489 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2490 g_free(with);
2491 return FALSE;
2494 if (msg->response != 200) {
2495 gchar *queued_msg = NULL;
2496 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2498 if (session->outgoing_message_queue) {
2499 queued_msg = session->outgoing_message_queue->data;
2501 sipe_present_message_undelivered_err(with, sip, queued_msg);
2503 im_session_destroy(sip, session);
2504 g_free(with);
2505 return FALSE;
2508 dialog = session->dialog;
2509 if (!dialog) {
2510 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2511 g_free(with);
2512 return FALSE;
2515 sipe_parse_dialog(msg, dialog, TRUE);
2516 dialog->cseq = 0;
2518 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2519 session->outgoing_invite = NULL;
2520 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2521 sipe_im_remove_first_from_queue(session);
2522 } else {
2523 sipe_im_process_queue(sip, session);
2526 g_free(with);
2527 return TRUE;
2531 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
2533 gchar *hdr;
2534 gchar *to;
2535 gchar *contact;
2536 gchar *body;
2537 char *msgformat;
2538 char *msgtext;
2539 char *base64_msg;
2540 char *ms_text_format;
2541 gchar *msgr_value;
2542 gchar *msgr;
2544 if (session->dialog) {
2545 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2546 return;
2549 session->dialog = g_new0(struct sip_dialog, 1);
2551 if (strstr(session->with, "sip:")) {
2552 to = g_strdup(session->with);
2553 } else {
2554 to = g_strdup_printf("sip:%s", session->with);
2557 sipe_parse_html(msg_body, &msgformat, &msgtext);
2558 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2560 msgr_value = sipmsg_get_msgr_string(msgformat);
2561 g_free(msgformat);
2562 msgr = "";
2563 if (msgr_value) {
2564 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2565 g_free(msgr_value);
2568 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2569 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2570 g_free(msgtext);
2571 g_free(msgr);
2572 g_free(base64_msg);
2574 contact = get_contact(sip);
2575 hdr = g_strdup_printf(
2576 "Contact: %s\r\n%s"
2577 "Content-Type: application/sdp\r\n",
2578 contact, ms_text_format);
2579 g_free(ms_text_format);
2581 body = g_strdup_printf(
2582 "v=0\r\n"
2583 "o=- 0 0 IN IP4 %s\r\n"
2584 "s=session\r\n"
2585 "c=IN IP4 %s\r\n"
2586 "t=0 0\r\n"
2587 "m=message %d sip null\r\n"
2588 "a=accept-types:text/plain text/html image/gif "
2589 "multipart/alternative application/im-iscomposing+xml\r\n",
2590 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2592 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2593 to, to, hdr, body, session->dialog, process_invite_response);
2595 g_free(to);
2596 g_free(body);
2597 g_free(hdr);
2598 g_free(contact);
2601 static void
2602 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2604 if (session) {
2605 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2606 im_session_destroy(sip, session);
2610 static void
2611 sipe_convo_closed(PurpleConnection * gc, const char *who)
2613 struct sipe_account_data *sip = gc->proto_data;
2615 purple_debug_info("sipe", "conversation with %s closed\n", who);
2616 im_session_close(sip, find_im_session(sip, who));
2619 static void
2620 im_session_close_all (struct sipe_account_data *sip)
2622 GSList *entry = sip->im_sessions;
2623 while (entry) {
2624 im_session_close (sip, entry->data);
2625 entry = sip->im_sessions;
2629 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2631 struct sipe_account_data *sip;
2632 char *to;
2633 char *text;
2634 struct sip_im_session *session;
2636 purple_debug_info("sipe", "sipe_im_send what=%s\n", what);
2638 sip = gc->proto_data;
2639 to = g_strdup(who);
2640 text = g_strdup(what);
2642 session = find_or_create_im_session(sip, who);
2644 // Queue the message
2645 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
2647 if (session->dialog && session->dialog->callid) {
2648 sipe_im_process_queue(sip, session);
2649 } else if (!session->outgoing_invite) {
2650 // Need to send the INVITE to get the outgoing dialog setup
2651 sipe_invite(sip, session, text);
2654 g_free(to);
2655 return 1;
2659 /* End IM Session (INVITE and MESSAGE methods) */
2661 static unsigned int
2662 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2664 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2665 struct sip_im_session *session;
2667 if (state == PURPLE_NOT_TYPING)
2668 return 0;
2670 session = find_im_session(sip, who);
2672 if (session && session->dialog) {
2673 send_sip_request(gc, "INFO", who, who,
2674 "Content-Type: application/xml\r\n",
2675 SIPE_SEND_TYPING, session->dialog, NULL);
2678 return SIPE_TYPING_SEND_TIMEOUT;
2681 static gboolean resend_timeout(struct sipe_account_data *sip)
2683 GSList *tmp = sip->transactions;
2684 time_t currtime = time(NULL);
2685 while (tmp) {
2686 struct transaction *trans = tmp->data;
2687 tmp = tmp->next;
2688 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2689 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2690 /* TODO 408 */
2691 } else {
2692 if ((currtime - trans->time > 2) && trans->retries == 0) {
2693 trans->retries++;
2694 sendout_sipmsg(sip, trans->msg);
2698 return TRUE;
2701 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2703 /* register again when security token expires */
2704 /* we have to start a new authentication as the security token
2705 * is almost expired by sending a not signed REGISTER message */
2706 purple_debug_info("sipe", "do a full reauthentication\n");
2707 sipe_auth_free(&sip->registrar);
2708 sip->registerstatus = 0;
2709 do_register(sip);
2710 sip->reauthenticate_set = FALSE;
2713 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2715 gchar *from;
2716 gchar *contenttype;
2717 gboolean found = FALSE;
2719 from = parse_from(sipmsg_find_header(msg, "From"));
2721 if (!from) return;
2723 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2725 contenttype = sipmsg_find_header(msg, "Content-Type");
2726 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2727 gchar *msgr = sipmsg_find_part_of_header(contenttype, "msgr=", NULL, NULL);
2728 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2730 gchar *body_esc = g_markup_escape_text(msg->body, -1);
2731 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2732 g_free(msgr);
2733 g_free(body_esc);
2734 g_free(x_mms_im_format);
2736 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2737 g_free(body_html);
2738 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2739 found = TRUE;
2740 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2741 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2742 xmlnode *state;
2743 gchar *statedata;
2745 if (!isc) {
2746 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2747 return;
2750 state = xmlnode_get_child(isc, "state");
2752 if (!state) {
2753 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2754 xmlnode_free(isc);
2755 return;
2758 statedata = xmlnode_get_data(state);
2759 if (statedata) {
2760 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2761 else serv_got_typing_stopped(sip->gc, from);
2763 g_free(statedata);
2765 xmlnode_free(isc);
2766 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2767 found = TRUE;
2769 if (!found) {
2770 purple_debug_info("sipe", "got unknown mime-type");
2771 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2773 g_free(from);
2776 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2778 gchar *ms_text_format;
2779 gchar *from;
2780 struct sip_im_session *session;
2781 // Only accept text invitations
2782 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2783 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2784 return;
2787 from = parse_from(sipmsg_find_header(msg, "From"));
2788 session = find_or_create_im_session (sip, from);
2789 if (session) {
2790 if (session->dialog) {
2791 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2792 } else {
2793 session->dialog = g_new0(struct sip_dialog, 1);
2795 sipe_parse_dialog(msg, session->dialog, FALSE);
2797 session->dialog->callid = sipmsg_find_header(msg, "Call-ID");
2798 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2799 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2800 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2802 } else {
2803 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2806 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2807 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2808 if (ms_text_format && !strncmp(ms_text_format, "text/plain", 10)) {
2809 gchar *msgr = sipmsg_find_part_of_header(ms_text_format, "msgr=", ";", NULL);
2810 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2812 gchar *ms_body = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
2813 g_free(msgr);
2814 if (ms_body) {
2815 gchar *body = purple_base64_decode(ms_body, NULL);
2816 gchar *body_esc = g_markup_escape_text(body, -1);
2817 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2818 g_free(ms_body);
2819 g_free(body_esc);
2820 g_free(body);
2821 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2822 g_free(body_html);
2823 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2825 g_free(x_mms_im_format);
2827 g_free(from);
2829 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2830 sipmsg_remove_header(msg, "Ms-Text-Format");
2831 sipmsg_remove_header(msg, "EndPoints");
2832 sipmsg_remove_header(msg, "User-Agent");
2833 sipmsg_remove_header(msg, "Roster-Manager");
2835 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2836 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2838 send_sip_response(sip->gc, msg, 200, "OK", g_strdup_printf(
2839 "v=0\r\n"
2840 "o=- 0 0 IN IP4 %s\r\n"
2841 "s=session\r\n"
2842 "c=IN IP4 %s\r\n"
2843 "t=0 0\r\n"
2844 "m=message %d sip sip:%s\r\n"
2845 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2846 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2847 sip->realport, sip->username));
2850 static void sipe_connection_cleanup(struct sipe_account_data *);
2851 static void create_connection(struct sipe_account_data *, gchar *, int);
2853 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2855 gchar *tmp;
2856 //gchar krb5_token;
2857 const gchar *expires_header;
2858 int expires;
2860 expires_header = sipmsg_find_header(msg, "Expires");
2861 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2862 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2864 switch (msg->response) {
2865 case 200:
2866 if (expires == 0) {
2867 sip->registerstatus = 0;
2868 } else {
2869 int i = 0;
2870 gchar *contact_hdr = NULL;
2871 gchar *gruu = NULL;
2872 gchar *epid;
2873 gchar *uuid;
2875 sip->registerexpire = expires;
2877 if (!sip->reregister_set) {
2878 gchar *action_name = g_strdup_printf("<%s>", "registration");
2879 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
2880 g_free(action_name);
2881 sip->reregister_set = TRUE;
2884 sip->registerstatus = 3;
2886 if (!sip->reauthenticate_set) {
2887 /* we have to reauthenticate as our security token expires
2888 after eight hours (be five minutes early) */
2889 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
2890 guint reauth_timeout = (8 * 3600) - 360;
2891 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
2892 g_free(action_name);
2893 sip->reauthenticate_set = TRUE;
2896 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
2898 epid = get_epid();
2899 uuid = generateUUIDfromEPID(epid);
2900 g_free(epid);
2902 // There can be multiple Contact headers (one per location where the user is logged in) so
2903 // make sure to only get the one for this uuid
2904 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
2905 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
2906 if (valid_contact) {
2907 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
2908 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
2909 g_free(valid_contact);
2910 break;
2911 } else {
2912 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
2915 g_free(uuid);
2917 if(gruu) {
2918 sip->contact = g_strdup_printf("<%s>", gruu);
2919 g_free(gruu);
2920 } else {
2921 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
2922 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);
2925 if (!sip->subscribed) { //do it just once, not every re-register
2926 tmp = sipmsg_find_header(msg, "Allow-Events");
2927 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
2928 sipe_subscribe_buddylist(sip, msg);
2930 sipe_subscribe_acl(sip, msg);
2931 sipe_subscribe_roaming_self(sip, msg);
2932 sipe_subscribe_roaming_provisioning(sip, msg);
2933 sipe_subscribe_pending_buddies(sip, msg);
2934 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
2935 sip->subscribed = TRUE;
2938 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
2939 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
2940 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
2941 } else {
2942 tmp = sipmsg_find_header(msg, "ms-keep-alive");
2943 if (tmp) {
2944 sipe_keep_alive_timeout(sip, tmp);
2948 // Should we remove the transaction here?
2949 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
2950 transactions_remove(sip, tc);
2952 break;
2953 case 301:
2955 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
2957 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
2958 gchar **parts = g_strsplit(redirect + 4, ";", 0);
2959 gchar **tmp;
2960 gchar *hostname;
2961 int port = 0;
2962 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
2963 int i = 1;
2965 tmp = g_strsplit(parts[0], ":", 0);
2966 hostname = g_strdup(tmp[0]);
2967 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
2968 g_strfreev(tmp);
2970 while (parts[i]) {
2971 tmp = g_strsplit(parts[i], "=", 0);
2972 if (tmp[1]) {
2973 if (g_strcasecmp("transport", tmp[0]) == 0) {
2974 if (g_strcasecmp("tcp", tmp[1]) == 0) {
2975 transport = SIPE_TRANSPORT_TCP;
2976 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
2977 transport = SIPE_TRANSPORT_UDP;
2981 g_strfreev(tmp);
2982 i++;
2984 g_strfreev(parts);
2986 /* Close old connection */
2987 sipe_connection_cleanup(sip);
2989 /* Create new connection */
2990 sip->transport = transport;
2991 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
2992 hostname, port, TRANSPORT_DESCRIPTOR);
2993 create_connection(sip, hostname, port);
2996 break;
2997 case 401:
2998 if (sip->registerstatus != 2) {
2999 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3000 if (sip->registrar.retries > 3) {
3001 sip->gc->wants_to_die = TRUE;
3002 purple_connection_error(sip->gc, _("Wrong Password"));
3003 return TRUE;
3005 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3006 tmp = sipmsg_find_auth_header(msg, "NTLM");
3007 } else {
3008 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3010 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3011 fill_auth(sip, tmp, &sip->registrar);
3012 sip->registerstatus = 2;
3013 if (sip->account->disconnecting) {
3014 do_register_exp(sip, 0);
3015 } else {
3016 do_register(sip);
3019 break;
3020 case 403:
3022 const gchar *warning = sipmsg_find_header(msg, "Warning");
3023 if (warning != NULL) {
3024 /* Example header:
3025 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3027 gchar **tmp = g_strsplit(warning, "\"", 0);
3028 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3029 g_strfreev(tmp);
3030 } else {
3031 warning = _("You have been rejected by the server");
3034 sip->gc->wants_to_die = TRUE;
3035 purple_connection_error(sip->gc, warning);
3036 return TRUE;
3038 break;
3039 case 404:
3041 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3042 if (warning != NULL) {
3043 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3044 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3045 g_free(reason);
3046 } else {
3047 warning = _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3050 sip->gc->wants_to_die = TRUE;
3051 purple_connection_error(sip->gc, warning);
3052 return TRUE;
3054 break;
3055 case 503:
3057 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3058 if (warning != NULL) {
3059 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3060 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3061 g_free(reason);
3062 } else {
3063 warning = _("Service unavailable: no reason given");
3066 sip->gc->wants_to_die = TRUE;
3067 purple_connection_error(sip->gc, warning);
3068 return TRUE;
3070 break;
3072 return TRUE;
3075 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3077 const char *uri;
3078 xmlnode *xn_categories;
3079 xmlnode *xn_category;
3080 xmlnode *xn_node;
3081 int changed = 0;
3082 const char *activity = NULL;
3084 xn_categories = xmlnode_from_str(data, len);
3085 uri = xmlnode_get_attrib(xn_categories, "uri");
3087 purple_debug_info("sipe", "process_incoming_notify_rlmi\n");
3089 for (xn_category = xmlnode_get_child(xn_categories, "category");
3090 xn_category ;
3091 xn_category = xmlnode_get_next_twin(xn_category) )
3093 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3095 if (!strcmp(attrVar, "note"))
3097 char *note;
3098 struct sipe_buddy *sbuddy;
3099 xn_node = xmlnode_get_child(xn_category, "note");
3100 if (!xn_node) continue;
3101 xn_node = xmlnode_get_child(xn_node, "body");
3102 if (!xn_node) continue;
3104 note = xmlnode_get_data(xn_node);
3106 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3107 if (sbuddy && note)
3109 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3110 sbuddy->annotation = g_strdup(note);
3111 changed = 1;
3114 g_free(note);
3116 else if(!strcmp(attrVar, "state"))
3118 char *data;
3119 int avail;
3120 xn_node = xmlnode_get_child(xn_category, "state");
3121 if (!xn_node) continue;
3122 xn_node = xmlnode_get_child(xn_node, "availability");
3123 if (!xn_node) continue;
3125 data = xmlnode_get_data(xn_node);
3126 avail = atoi(data);
3127 g_free(data);
3129 if (avail < 3000)
3130 activity = "unknown";
3131 else if (avail < 4500)
3132 activity = "available";
3133 else if (avail < 6000)
3134 activity = "idle";
3135 else if (avail < 7500)
3136 activity = "busy";
3137 else if (avail < 9000)
3138 activity = "busy";
3139 else if (avail < 12000)
3140 activity = "dnd";
3141 else if (avail < 18000)
3142 activity = "away";
3143 else
3144 activity = "offline";
3146 changed = 1;
3149 if (changed)
3151 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3154 xmlnode_free(xn_categories);
3157 static void process_incoming_notify_pidf(struct sipe_account_data *sip, struct sipmsg *msg)
3159 const gchar *uri;
3160 gchar *getbasic = g_strdup("closed");
3161 gchar *activity = g_strdup("available");
3162 xmlnode *pidf;
3163 xmlnode *basicstatus = NULL, *tuple, *status;
3164 gboolean isonline = FALSE;
3165 xmlnode *display_name_node;
3167 pidf = xmlnode_from_str(msg->body, msg->bodylen);
3168 if (!pidf) {
3169 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",msg->body);
3170 return;
3173 uri = xmlnode_get_attrib(pidf, "entity");
3175 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3177 if ((status = xmlnode_get_child(tuple, "status"))) {
3178 basicstatus = xmlnode_get_child(status, "basic");
3182 if (!basicstatus) {
3183 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3184 xmlnode_free(pidf);
3185 return;
3188 getbasic = xmlnode_get_data(basicstatus);
3189 if (!getbasic) {
3190 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3191 xmlnode_free(pidf);
3192 return;
3195 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3196 if (strstr(getbasic, "open")) {
3197 isonline = TRUE;
3200 display_name_node = xmlnode_get_child(pidf, "display-name");
3201 // updating display name if alias was just URI
3202 if (display_name_node) {
3203 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3204 GSList *entry = buddies;
3205 PurpleBuddy *p_buddy;
3206 char * display_name = xmlnode_get_data(display_name_node);
3208 while (entry) {
3209 const char *server_alias;
3210 char *alias;
3212 p_buddy = entry->data;
3214 alias = (char *)purple_buddy_get_alias(p_buddy);
3215 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3216 if (!g_ascii_strcasecmp(uri, alias)) {
3217 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3218 purple_blist_alias_buddy(p_buddy, display_name);
3220 g_free(alias);
3222 server_alias = purple_buddy_get_server_alias(p_buddy);
3223 if (display_name &&
3224 ( (server_alias && strcmp(display_name, server_alias))
3225 || !server_alias || strlen(server_alias) == 0 )
3227 purple_blist_server_alias_buddy(p_buddy, display_name);
3230 entry = entry->next;
3234 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3235 if ((status = xmlnode_get_child(tuple, "status"))) {
3236 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3237 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3238 activity = xmlnode_get_data(basicstatus);
3244 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3246 if (isonline) {
3247 gchar * status_id = NULL;
3248 if (activity) {
3249 if (strstr(activity, "busy")) {
3250 status_id = "busy";
3251 } else if (strstr(activity, "away")) {
3252 status_id = "away";
3256 if (!status_id) {
3257 status_id = "available";
3260 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3261 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3262 } else {
3263 purple_prpl_got_user_status(sip->account, uri, "offline", NULL);
3266 xmlnode_free(pidf);
3267 g_free(getbasic);
3268 g_free(activity);
3271 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, struct sipmsg *msg)
3273 const char *availability;
3274 const char *activity;
3275 const char *display_name = NULL;
3276 const char *activity_name;
3277 const char *name;
3278 char *uri;
3279 int avl;
3280 int act;
3281 struct sipe_buddy *sbuddy;
3283 xmlnode *xn_presentity = xmlnode_from_str(msg->body, msg->bodylen);
3285 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3286 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3287 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3288 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3289 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3290 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3291 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3292 const char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3293 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3294 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3295 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3296 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3298 name = xmlnode_get_attrib(xn_presentity, "uri");
3299 uri = g_strdup_printf("sip:%s", name);
3300 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3301 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3303 // updating display name if alias was just URI
3304 if (xn_display_name) {
3305 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3306 GSList *entry = buddies;
3307 PurpleBuddy *p_buddy;
3308 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3310 while (entry) {
3311 const char *email_str, *server_alias;
3313 p_buddy = entry->data;
3315 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3316 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3317 purple_blist_alias_buddy(p_buddy, display_name);
3320 server_alias = purple_buddy_get_server_alias(p_buddy);
3321 if (display_name &&
3322 ( (server_alias && strcmp(display_name, server_alias))
3323 || !server_alias || strlen(server_alias) == 0 )
3325 purple_blist_server_alias_buddy(p_buddy, display_name);
3328 if (email) {
3329 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3330 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3331 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3335 entry = entry->next;
3339 avl = atoi(availability);
3340 act = atoi(activity);
3342 if (act <= 100)
3343 activity_name = "away";
3344 else if (act <= 150)
3345 activity_name = "out-to-lunch";
3346 else if (act <= 300)
3347 activity_name = "be-right-back";
3348 else if (act <= 400)
3349 activity_name = "available";
3350 else if (act <= 500)
3351 activity_name = "on-the-phone";
3352 else if (act <= 600)
3353 activity_name = "busy";
3354 else
3355 activity_name = "available";
3357 if (avl == 0)
3358 activity_name = "offline";
3360 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3361 if (sbuddy)
3363 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3364 sbuddy->annotation = NULL;
3365 if (note) { sbuddy->annotation = g_strdup(note); }
3367 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3368 sbuddy->device_name = NULL;
3369 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3372 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3373 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3374 xmlnode_free(xn_presentity);
3375 g_free(uri);
3378 static void process_incoming_notify_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3380 char *ctype = sipmsg_find_header(msg, "Content-Type");
3382 purple_debug_info("sipe", "process_incoming_notify_presence: Content-Type: %s\n", ctype ? ctype : "");
3384 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3385 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3387 const char *content = msg->body;
3388 unsigned length = msg->bodylen;
3389 PurpleMimeDocument *mime = NULL;
3390 //char *subscription_state;
3392 if (strstr(ctype, "multipart"))
3394 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3395 GList* parts;
3396 mime = purple_mime_document_parse(doc);
3397 parts = purple_mime_document_get_parts(mime);
3398 content = purple_mime_part_get_data(parts->data);
3399 length = purple_mime_part_get_length(parts->data);
3400 g_free(doc);
3403 process_incoming_notify_rlmi(sip, content, length);
3405 if (mime)
3407 purple_mime_document_free(mime);
3410 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3412 process_incoming_notify_msrtc(sip, msg);
3414 else
3416 process_incoming_notify_pidf(sip, msg);
3421 * Dispatcher for all incoming subscription information
3422 * whether it comes from NOTIFY, BENOTIFY requests or
3423 * piggy-backed to subscription's OK responce.
3425 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3426 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3428 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3430 gchar *event = sipmsg_find_header(msg, "Event");
3431 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3432 const char *uri,*state;
3433 xmlnode *xn_list;
3434 xmlnode *xn_resource;
3435 xmlnode *xn_instance;
3437 int expires = 0;
3439 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3440 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3442 if (!request)
3444 const gchar *expires_header;
3445 expires_header = sipmsg_find_header(msg, "Expires");
3446 expires = expires_header ? strtol(expires_header, NULL, 10) : 0;
3447 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires);
3450 if (!subscription_state || strstr(subscription_state, "active"))
3452 if (event && strstr(event, "presence"))
3454 process_incoming_notify_presence(sip, msg);
3456 else if (event && strstr(event, "vnd-microsoft-roaming-contacts"))
3458 sipe_process_roaming_contacts(sip, msg, NULL);
3460 else if (event && strstr(event, "vnd-microsoft-roaming-self"))
3462 sipe_process_roaming_self(sip, msg);
3464 else if (event && strstr(event, "vnd-microsoft-roaming-ACL"))
3466 sipe_process_roaming_acl(sip, msg);
3468 else if (event && strstr(event, "presence.wpending"))
3470 sipe_process_incoming_pending(sip, msg);
3472 else
3474 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3478 //Subscription terminated and is not a (BE)NOTIFY; we need resub
3479 if ( !request && !benotify && subscription_state && strstr(subscription_state, "terminated"))
3481 if(event && strstr(event, "presence")){
3483 const char *content = msg->body;
3484 unsigned length = msg->bodylen;
3485 PurpleMimeDocument *mime = NULL;
3486 char *ctype = sipmsg_find_header(msg, "Content-Type");
3488 purple_debug_info("sipe", "process_incoming_notify: ctype(%s)\n",ctype);
3490 if (ctype && strstr(ctype, "multipart"))
3492 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3493 GList* parts;
3494 mime = purple_mime_document_parse(doc);
3495 parts = purple_mime_document_get_parts(mime);
3496 content = purple_mime_part_get_data(parts->data);
3497 length = purple_mime_part_get_length(parts->data);
3498 g_free(doc);
3501 xn_list = xmlnode_from_str(content, length);
3502 xn_resource = xmlnode_get_child(xn_list, "resource");
3503 if (!xn_resource) return;
3504 xn_instance = xmlnode_get_child(xn_resource, "instance");
3505 if (!xn_instance) return;
3506 state = xmlnode_get_attrib(xn_instance, "state");
3507 uri = xmlnode_get_attrib(xn_instance, "cid");
3509 purple_debug_info("sipe", "process_incoming_notify: cid(%s),state(%s)\n",uri,state);
3511 if(strstr(state, "resubscribe"))
3513 int timeout = expires ? expires : 3;
3514 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", uri);
3515 sipe_cancel_scheduled_action(sip, action_name);
3516 purple_debug_info("sipe", "process_incoming_notify: Subscription to buddy %s was terminated. Resubscribing\n", uri);
3517 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_to_name_single, sip, g_strdup(uri));
3518 g_free(action_name);
3521 else
3523 purple_debug_info("sipe", "Unable to process subscription termination. Event is not supported:%s\n",
3524 event ? event : "");
3528 //The server sends a (BE)NOTIFY with the status 'terminated'
3529 if(request && subscription_state && strstr(subscription_state, "terminated") )
3531 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3532 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3533 g_free(from);
3536 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3537 if (request && !benotify)
3539 sipmsg_remove_header(msg, "Expires");
3540 sipmsg_remove_header(msg, "subscription-state");
3541 sipmsg_remove_header(msg, "Event");
3542 sipmsg_remove_header(msg, "Require");
3543 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3548 * unused. Needed?
3550 static gchar* gen_xpidf(struct sipe_account_data *sip)
3552 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3553 "<presence>\r\n"
3554 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3555 "<display name=\"sip:%s\"/>\r\n"
3556 "<atom id=\"1234\">\r\n"
3557 "<address uri=\"sip:%s\">\r\n"
3558 "<status status=\"%s\"/>\r\n"
3559 "</address>\r\n"
3560 "</atom>\r\n"
3561 "</presence>\r\n",
3562 sip->username,
3563 sip->username,
3564 sip->username,
3565 sip->status);
3566 return doc;
3571 static gchar* gen_pidf(struct sipe_account_data *sip)
3573 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3574 "<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"
3575 "<tuple id=\"0\">\r\n"
3576 "<status>\r\n"
3577 "<basic>open</basic>\r\n"
3578 "<ep:activities>\r\n"
3579 " <ep:activity>%s</ep:activity>\r\n"
3580 "</ep:activities>"
3581 "</status>\r\n"
3582 "</tuple>\r\n"
3583 "<ci:display-name>%s</ci:display-name>\r\n"
3584 "</presence>",
3585 sip->username,
3586 sip->status,
3587 sip->username);
3588 return doc;
3592 static gboolean
3593 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3595 if (msg->response == 488) {
3596 sip->presence_method_version = 1;
3597 send_presence_info(sip);
3599 return TRUE;
3602 static void send_presence_info_v0(struct sipe_account_data *sip, char * note)
3604 int availability = 300; // online
3605 int activity = 400; // Available
3606 gchar *name;
3607 gchar *body;
3608 if (!strcmp(sip->status, "away")) {
3609 activity = 100;
3610 } else if (!strcmp(sip->status, "out-to-lunch")) {
3611 activity = 150;
3612 } else if (!strcmp(sip->status, "be-right-back")) {
3613 activity = 300;
3614 } else if (!strcmp(sip->status, "on-the-phone")) {
3615 activity = 500;
3616 } else if (!strcmp(sip->status, "do-not-disturb")) {
3617 activity = 600;
3618 } else if (!strcmp(sip->status, "busy")) {
3619 activity = 600;
3620 } else if (!strcmp(sip->status, "invisible")) {
3621 availability = 0; // offline
3622 activity = 100;
3625 name = g_strdup_printf("sip: sip:%s", sip->username);
3626 //@TODO: send user data - state; add hostname in upper case
3627 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3628 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
3629 g_free(name);
3630 g_free(body);
3633 static gboolean
3634 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3636 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3637 if (msg->response == 200) {
3638 sip->status_version = 0;
3639 send_presence_info(sip);
3641 return TRUE;
3644 static gboolean
3645 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3647 if (msg->response == 409) {
3648 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3649 // TODO need to parse the version #'s?
3650 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3651 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3652 gchar *tmp;
3653 gchar *hdr;
3655 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg->body);
3657 tmp = get_contact(sip);
3658 hdr = g_strdup_printf("Contact: %s\r\n"
3659 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3661 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3663 g_free(tmp);
3664 g_free(hdr);
3665 g_free(uri);
3666 g_free(doc);
3668 return TRUE;
3671 static void send_presence_info_v1(struct sipe_account_data *sip, char * note)
3673 int code;
3674 gchar *uri;
3675 gchar *doc;
3676 gchar *tmp;
3677 gchar *hdr;
3678 if (!strcmp(sip->status, "away")) {
3679 code = 12000;
3680 } else if (!strcmp(sip->status, "busy")) {
3681 code = 6000;
3682 } else {
3683 // Available
3684 code = 3000;
3687 uri = g_strdup_printf("sip:%s", sip->username);
3688 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3689 sip->status_version, code,
3690 sip->status_version, code,
3691 sip->status_version, note ? note : "",
3692 sip->status_version, note ? note : "",
3693 sip->status_version, note ? note : ""
3695 sip->status_version++;
3697 tmp = get_contact(sip);
3698 hdr = g_strdup_printf("Contact: %s\r\n"
3699 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3701 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
3703 g_free(tmp);
3704 g_free(hdr);
3705 g_free(uri);
3706 g_free(doc);
3709 static void send_presence_info(struct sipe_account_data *sip)
3711 PurpleStatus * status = purple_account_get_active_status(sip->account);
3712 gchar *note;
3713 if (!status) return;
3715 note = g_strdup(purple_status_get_attr_string(status, "message"));
3717 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
3718 if (sip->presence_method_version != 1) {
3719 send_presence_info_v0(sip, note);
3720 } else {
3721 send_presence_info_v1(sip, note);
3725 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3727 gboolean found = FALSE;
3728 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3729 if (msg->response == 0) { /* request */
3730 if (!strcmp(msg->method, "MESSAGE")) {
3731 process_incoming_message(sip, msg);
3732 found = TRUE;
3733 } else if (!strcmp(msg->method, "NOTIFY")) {
3734 purple_debug_info("sipe","send->process_incoming_notify\n");
3735 process_incoming_notify(sip, msg, TRUE, FALSE);
3736 found = TRUE;
3737 } else if (!strcmp(msg->method, "BENOTIFY")) {
3738 purple_debug_info("sipe","send->process_incoming_benotify\n");
3739 process_incoming_notify(sip, msg, TRUE, TRUE);
3740 found = TRUE;
3741 } else if (!strcmp(msg->method, "INVITE")) {
3742 process_incoming_invite(sip, msg);
3743 found = TRUE;
3744 } else if (!strcmp(msg->method, "INFO")) {
3745 // TODO needs work
3746 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3747 if (from) {
3748 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3750 g_free(from);
3751 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3752 found = TRUE;
3753 } else if (!strcmp(msg->method, "ACK")) {
3754 // ACK's don't need any response
3755 found = TRUE;
3756 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3757 // LCS 2005 sends us these - just respond 200 OK
3758 found = TRUE;
3759 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3760 } else if (!strcmp(msg->method, "BYE")) {
3761 struct sip_im_session *session;
3762 gchar *from;
3763 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3765 from = parse_from(sipmsg_find_header(msg, "From"));
3766 session = find_im_session (sip, from);
3767 g_free(from);
3769 if (session) {
3770 // TODO Let the user know the other user left the conversation?
3771 im_session_destroy(sip, session);
3774 found = TRUE;
3775 } else {
3776 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3778 } else { /* response */
3779 struct transaction *trans = transactions_find(sip, msg);
3780 if (trans) {
3781 if (msg->response == 407) {
3782 gchar *resend, *auth, *ptmp;
3784 if (sip->proxy.retries > 30) return;
3785 sip->proxy.retries++;
3786 /* do proxy authentication */
3788 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3790 fill_auth(sip, ptmp, &sip->proxy);
3791 auth = auth_header(sip, &sip->proxy, trans->msg);
3792 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3793 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
3794 g_free(auth);
3795 resend = sipmsg_to_string(trans->msg);
3796 /* resend request */
3797 sendout_pkt(sip->gc, resend);
3798 g_free(resend);
3799 } else {
3800 if (msg->response == 100 || msg->response == 180) {
3801 /* ignore provisional response */
3802 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
3803 } else {
3804 sip->proxy.retries = 0;
3805 if (!strcmp(trans->msg->method, "REGISTER")) {
3806 if (msg->response == 401)
3808 sip->registrar.retries++;
3809 sip->registrar.expires = 0;
3811 else
3813 sip->registrar.retries = 0;
3815 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
3816 } else {
3817 if (msg->response == 401) {
3818 gchar *resend, *auth, *ptmp;
3820 if (sip->registrar.retries > 4) return;
3821 sip->registrar.retries++;
3823 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3824 ptmp = sipmsg_find_auth_header(msg, "NTLM");
3825 } else {
3826 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
3829 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
3831 fill_auth(sip, ptmp, &sip->registrar);
3832 auth = auth_header(sip, &sip->registrar, trans->msg);
3833 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3834 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
3836 //sipmsg_remove_header(trans->msg, "Authorization");
3837 //sipmsg_add_header(trans->msg, "Authorization", auth);
3838 g_free(auth);
3839 resend = sipmsg_to_string(trans->msg);
3840 /* resend request */
3841 sendout_pkt(sip->gc, resend);
3842 g_free(resend);
3846 if (trans->callback) {
3847 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
3848 /* call the callback to process response*/
3849 (trans->callback)(sip, msg, trans);
3851 /* Not sure if this is needed or what needs to be done
3852 but transactions seem to be removed prematurely so
3853 this only removes them if the response is 200 OK */
3854 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
3855 /*Has a bug and it's unneccesary*/
3856 /*transactions_remove(sip, trans);*/
3860 found = TRUE;
3861 } else {
3862 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
3865 if (!found) {
3866 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
3870 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
3872 char *cur;
3873 char *dummy;
3874 struct sipmsg *msg;
3875 int restlen;
3876 cur = conn->inbuf;
3878 /* according to the RFC remove CRLF at the beginning */
3879 while (*cur == '\r' || *cur == '\n') {
3880 cur++;
3882 if (cur != conn->inbuf) {
3883 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
3884 conn->inbufused = strlen(conn->inbuf);
3887 /* Received a full Header? */
3888 sip->processing_input = TRUE;
3889 while (sip->processing_input &&
3890 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
3891 time_t currtime = time(NULL);
3892 cur += 2;
3893 cur[0] = '\0';
3894 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
3895 msg = sipmsg_parse_header(conn->inbuf);
3896 cur[0] = '\r';
3897 cur += 2;
3898 restlen = conn->inbufused - (cur - conn->inbuf);
3899 if (restlen >= msg->bodylen) {
3900 dummy = g_malloc(msg->bodylen + 1);
3901 memcpy(dummy, cur, msg->bodylen);
3902 dummy[msg->bodylen] = '\0';
3903 msg->body = dummy;
3904 cur += msg->bodylen;
3905 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
3906 conn->inbufused = strlen(conn->inbuf);
3907 } else {
3908 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
3909 restlen, msg->bodylen, (int)strlen(conn->inbuf));
3910 sipmsg_free(msg);
3911 return;
3914 /*if (msg->body) {
3915 purple_debug_info("sipe", "body:\n%s", msg->body);
3918 // Verify the signature before processing it
3919 if (sip->registrar.ntlm_key) {
3920 struct sipmsg_breakdown msgbd;
3921 gchar *signature_input_str;
3922 gchar *signature = NULL;
3923 gchar *rspauth;
3924 msgbd.msg = msg;
3925 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
3926 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
3927 if (signature_input_str != NULL) {
3928 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
3930 g_free(signature_input_str);
3932 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
3934 if (signature != NULL) {
3935 if (rspauth != NULL) {
3936 if (purple_ntlm_verify_signature (signature, rspauth)) {
3937 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
3938 process_input_message(sip, msg);
3939 } else {
3940 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
3941 purple_connection_error(sip->gc, _("Invalid message signature received"));
3942 sip->gc->wants_to_die = TRUE;
3944 } else if (msg->response == 401) {
3945 purple_connection_error(sip->gc, _("Wrong Password"));
3946 sip->gc->wants_to_die = TRUE;
3948 g_free(signature);
3951 g_free(rspauth);
3952 sipmsg_breakdown_free(&msgbd);
3953 } else {
3954 process_input_message(sip, msg);
3957 sipmsg_free(msg);
3961 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
3963 PurpleConnection *gc = data;
3964 struct sipe_account_data *sip = gc->proto_data;
3965 struct sipmsg *msg;
3966 int len;
3967 time_t currtime;
3969 static char buffer[65536];
3970 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
3971 buffer[len] = '\0';
3972 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
3973 msg = sipmsg_parse_msg(buffer);
3974 if (msg) process_input_message(sip, msg);
3978 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
3980 struct sipe_account_data *sip = gc->proto_data;
3981 PurpleSslConnection *gsc = sip->gsc;
3983 purple_debug_error("sipe", "%s",debug);
3984 purple_connection_error(gc, msg);
3986 /* Invalidate this connection. Next send will open a new one */
3987 if (gsc) {
3988 connection_remove(sip, gsc->fd);
3989 purple_ssl_close(gsc);
3991 sip->gsc = NULL;
3992 sip->fd = -1;
3995 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
3997 PurpleConnection *gc = data;
3998 struct sipe_account_data *sip;
3999 struct sip_connection *conn;
4000 int readlen, len;
4001 gboolean firstread = TRUE;
4003 /* NOTE: This check *IS* necessary */
4004 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4005 purple_ssl_close(gsc);
4006 return;
4009 sip = gc->proto_data;
4010 conn = connection_find(sip, gsc->fd);
4011 if (conn == NULL) {
4012 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4013 gc->wants_to_die = TRUE;
4014 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4015 return;
4018 /* Read all available data from the SSL connection */
4019 do {
4020 /* Increase input buffer size as needed */
4021 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4022 conn->inbuflen += SIMPLE_BUF_INC;
4023 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4024 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4027 /* Try to read as much as there is space left in the buffer */
4028 readlen = conn->inbuflen - conn->inbufused - 1;
4029 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4031 if (len < 0 && errno == EAGAIN) {
4032 /* Try again later */
4033 return;
4034 } else if (len < 0) {
4035 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4036 return;
4037 } else if (firstread && (len == 0)) {
4038 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4039 return;
4042 conn->inbufused += len;
4043 firstread = FALSE;
4045 /* Equivalence indicates that there is possibly more data to read */
4046 } while (len == readlen);
4048 conn->inbuf[conn->inbufused] = '\0';
4049 process_input(sip, conn);
4053 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4055 PurpleConnection *gc = data;
4056 struct sipe_account_data *sip = gc->proto_data;
4057 int len;
4058 struct sip_connection *conn = connection_find(sip, source);
4059 if (!conn) {
4060 purple_debug_error("sipe", "Connection not found!\n");
4061 return;
4064 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4065 conn->inbuflen += SIMPLE_BUF_INC;
4066 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4069 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4071 if (len < 0 && errno == EAGAIN)
4072 return;
4073 else if (len <= 0) {
4074 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4075 connection_remove(sip, source);
4076 if (sip->fd == source) sip->fd = -1;
4077 return;
4080 conn->inbufused += len;
4081 conn->inbuf[conn->inbufused] = '\0';
4083 process_input(sip, conn);
4086 /* Callback for new connections on incoming TCP port */
4087 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4089 PurpleConnection *gc = data;
4090 struct sipe_account_data *sip = gc->proto_data;
4091 struct sip_connection *conn;
4093 int newfd = accept(source, NULL, NULL);
4095 conn = connection_create(sip, newfd);
4097 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4100 static void login_cb(gpointer data, gint source, const gchar *error_message)
4102 PurpleConnection *gc = data;
4103 struct sipe_account_data *sip;
4104 struct sip_connection *conn;
4106 if (!PURPLE_CONNECTION_IS_VALID(gc))
4108 if (source >= 0)
4109 close(source);
4110 return;
4113 if (source < 0) {
4114 purple_connection_error(gc, _("Could not connect"));
4115 return;
4118 sip = gc->proto_data;
4119 sip->fd = source;
4120 sip->last_keepalive = time(NULL);
4122 conn = connection_create(sip, source);
4124 do_register(sip);
4126 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4129 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4131 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4132 if (sip == NULL) return;
4134 do_register(sip);
4137 static guint sipe_ht_hash_nick(const char *nick)
4139 char *lc = g_utf8_strdown(nick, -1);
4140 guint bucket = g_str_hash(lc);
4141 g_free(lc);
4143 return bucket;
4146 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4148 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4151 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4153 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4155 sip->listen_data = NULL;
4157 if (listenfd == -1) {
4158 purple_connection_error(sip->gc, _("Could not create listen socket"));
4159 return;
4162 sip->fd = listenfd;
4164 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4165 sip->listenfd = sip->fd;
4167 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4169 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4170 do_register(sip);
4173 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4175 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4176 int addr_size;
4178 sip->query_data = NULL;
4180 if (!hosts || !hosts->data) {
4181 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4182 return;
4185 addr_size = GPOINTER_TO_INT(hosts->data);
4186 hosts = g_slist_remove(hosts, hosts->data);
4187 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4188 g_free(hosts->data);
4189 hosts = g_slist_remove(hosts, hosts->data);
4190 while (hosts) {
4191 hosts = g_slist_remove(hosts, hosts->data);
4192 g_free(hosts->data);
4193 hosts = g_slist_remove(hosts, hosts->data);
4196 /* create socket for incoming connections */
4197 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4198 sipe_udp_host_resolved_listen_cb, sip);
4199 if (sip->listen_data == NULL) {
4200 purple_connection_error(sip->gc, _("Could not create listen socket"));
4201 return;
4205 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4206 gpointer data)
4208 PurpleConnection *gc = data;
4209 struct sipe_account_data *sip;
4211 /* If the connection is already disconnected, we don't need to do anything else */
4212 if (!PURPLE_CONNECTION_IS_VALID(gc))
4213 return;
4215 sip = gc->proto_data;
4216 sip->fd = -1;
4217 sip->gsc = NULL;
4219 switch(error) {
4220 case PURPLE_SSL_CONNECT_FAILED:
4221 purple_connection_error(gc, _("Connection Failed"));
4222 break;
4223 case PURPLE_SSL_HANDSHAKE_FAILED:
4224 purple_connection_error(gc, _("SSL Handshake Failed"));
4225 break;
4226 case PURPLE_SSL_CERTIFICATE_INVALID:
4227 purple_connection_error(gc, _("SSL Certificate Invalid"));
4228 break;
4232 static void
4233 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4235 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4236 PurpleProxyConnectData *connect_data;
4238 sip->listen_data = NULL;
4240 sip->listenfd = listenfd;
4241 if (sip->listenfd == -1) {
4242 purple_connection_error(sip->gc, _("Could not create listen socket"));
4243 return;
4246 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4247 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4248 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4249 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4250 sipe_newconn_cb, sip->gc);
4251 purple_debug_info("sipe", "connecting to %s port %d\n",
4252 sip->realhostname, sip->realport);
4253 /* open tcp connection to the server */
4254 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4255 sip->realport, login_cb, sip->gc);
4257 if (connect_data == NULL) {
4258 purple_connection_error(sip->gc, _("Couldn't create socket"));
4263 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4265 PurpleAccount *account = sip->account;
4266 PurpleConnection *gc = sip->gc;
4268 if (purple_account_get_bool(account, "useport", FALSE)) {
4269 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4270 port = purple_account_get_int(account, "port", 0);
4271 } else {
4272 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4275 sip->realhostname = hostname;
4276 sip->realport = port;
4278 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4279 hostname, port);
4281 /* TODO: is there a good default grow size? */
4282 if (sip->transport != SIPE_TRANSPORT_UDP)
4283 sip->txbuf = purple_circ_buffer_new(0);
4285 if (sip->transport == SIPE_TRANSPORT_TLS) {
4286 /* SSL case */
4287 if (!purple_ssl_is_supported()) {
4288 gc->wants_to_die = TRUE;
4289 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4290 return;
4293 purple_debug_info("sipe", "using SSL\n");
4295 sip->gsc = purple_ssl_connect(account, hostname, port,
4296 login_cb_ssl, sipe_ssl_connect_failure, gc);
4297 if (sip->gsc == NULL) {
4298 purple_connection_error(gc, _("Could not create SSL context"));
4299 return;
4301 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4302 /* UDP case */
4303 purple_debug_info("sipe", "using UDP\n");
4305 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4306 if (sip->query_data == NULL) {
4307 purple_connection_error(gc, _("Could not resolve hostname"));
4309 } else {
4310 /* TCP case */
4311 purple_debug_info("sipe", "using TCP\n");
4312 /* create socket for incoming connections */
4313 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4314 sipe_tcp_connect_listen_cb, sip);
4315 if (sip->listen_data == NULL) {
4316 purple_connection_error(gc, _("Could not create listen socket"));
4317 return;
4322 /* Service list for autodection */
4323 static const struct sipe_service_data service_autodetect[] = {
4324 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4325 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4326 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4327 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4328 { NULL, NULL, 0 }
4331 /* Service list for SSL/TLS */
4332 static const struct sipe_service_data service_tls[] = {
4333 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4334 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4335 { NULL, NULL, 0 }
4338 /* Service list for TCP */
4339 static const struct sipe_service_data service_tcp[] = {
4340 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4341 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4342 { NULL, NULL, 0 }
4345 /* Service list for UDP */
4346 static const struct sipe_service_data service_udp[] = {
4347 { "sip", "udp", SIPE_TRANSPORT_UDP },
4348 { NULL, NULL, 0 }
4351 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4352 static void resolve_next_service(struct sipe_account_data *sip,
4353 const struct sipe_service_data *start)
4355 if (start) {
4356 sip->service_data = start;
4357 } else {
4358 sip->service_data++;
4359 if (sip->service_data->service == NULL) {
4360 gchar *hostname;
4361 /* Try connecting to the SIP hostname directly */
4362 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4363 if (sip->auto_transport) {
4364 // If SSL is supported, default to using it; OCS servers aren't configured
4365 // by default to accept TCP
4366 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4367 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4368 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4371 hostname = g_strdup(sip->sipdomain);
4372 create_connection(sip, hostname, 0);
4373 return;
4377 /* Try to resolve next service */
4378 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4379 sip->service_data->transport,
4380 sip->sipdomain,
4381 srvresolved, sip);
4384 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4386 struct sipe_account_data *sip = data;
4388 sip->srv_query_data = NULL;
4390 /* find the host to connect to */
4391 if (results) {
4392 gchar *hostname = g_strdup(resp->hostname);
4393 int port = resp->port;
4394 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4395 hostname, port);
4396 g_free(resp);
4398 sip->transport = sip->service_data->type;
4400 create_connection(sip, hostname, port);
4401 } else {
4402 resolve_next_service(sip, NULL);
4406 static void sipe_login(PurpleAccount *account)
4408 PurpleConnection *gc;
4409 struct sipe_account_data *sip;
4410 gchar **signinname_login, **userserver, **domain_user;
4411 const char *transport;
4413 const char *username = purple_account_get_username(account);
4414 gc = purple_account_get_connection(account);
4416 if (strpbrk(username, " \t\v\r\n") != NULL) {
4417 gc->wants_to_die = TRUE;
4418 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4419 return;
4422 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4423 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4424 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4425 sip->gc = gc;
4426 sip->account = account;
4427 sip->registerexpire = 900;
4428 sip->reregister_set = FALSE;
4429 sip->reauthenticate_set = FALSE;
4430 sip->subscribed = FALSE;
4431 sip->subscribed_buddies = FALSE;
4433 signinname_login = g_strsplit(username, ",", 2);
4435 userserver = g_strsplit(signinname_login[0], "@", 2);
4436 purple_connection_set_display_name(gc, userserver[0]);
4437 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4438 sip->sipdomain = g_strdup(userserver[1]);
4440 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4441 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4442 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4444 sip->password = g_strdup(purple_connection_get_password(gc));
4446 g_strfreev(userserver);
4447 g_strfreev(domain_user);
4448 g_strfreev(signinname_login);
4450 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4452 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4454 /* TODO: Set the status correctly. */
4455 sip->status = g_strdup("available");
4457 transport = purple_account_get_string(account, "transport", "auto");
4458 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4459 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4460 SIPE_TRANSPORT_UDP;
4462 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4463 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4464 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4465 } else if (strcmp(transport, "auto") == 0) {
4466 sip->auto_transport = TRUE;
4467 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4468 } else if (strcmp(transport, "tls") == 0) {
4469 resolve_next_service(sip, service_tls);
4470 } else if (strcmp(transport, "tcp") == 0) {
4471 resolve_next_service(sip, service_tcp);
4472 } else {
4473 resolve_next_service(sip, service_udp);
4477 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4479 connection_free_all(sip);
4481 if (sip->query_data != NULL)
4482 purple_dnsquery_destroy(sip->query_data);
4483 sip->query_data == NULL;
4485 if (sip->srv_query_data != NULL)
4486 purple_srv_cancel(sip->srv_query_data);
4487 sip->srv_query_data = NULL;
4489 if (sip->listen_data != NULL)
4490 purple_network_listen_cancel(sip->listen_data);
4491 sip->listen_data = NULL;
4493 if (sip->gsc != NULL)
4494 purple_ssl_close(sip->gsc);
4495 sip->gsc = NULL;
4497 sipe_auth_free(&sip->registrar);
4498 sipe_auth_free(&sip->proxy);
4500 if (sip->txbuf)
4501 purple_circ_buffer_destroy(sip->txbuf);
4502 sip->txbuf = NULL;
4504 g_free(sip->realhostname);
4505 sip->realhostname = NULL;
4507 if (sip->listenpa)
4508 purple_input_remove(sip->listenpa);
4509 sip->listenpa = 0;
4510 if (sip->tx_handler)
4511 purple_input_remove(sip->tx_handler);
4512 sip->tx_handler = 0;
4513 if (sip->resendtimeout)
4514 purple_timeout_remove(sip->resendtimeout);
4515 sip->resendtimeout = 0;
4516 if (sip->timeouts) {
4517 GSList *entry = sip->timeouts;
4518 while (entry) {
4519 struct scheduled_action *sched_action = entry->data;
4520 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4521 purple_timeout_remove(sched_action->timeout_handler);
4522 g_free(sched_action->payload);
4523 g_free(sched_action->name);
4524 g_free(sched_action);
4525 entry = entry->next;
4528 g_slist_free(sip->timeouts);
4530 if (sip->contact)
4531 g_free(sip->contact);
4532 sip->contact = NULL;
4533 if (sip->regcallid)
4534 g_free(sip->regcallid);
4535 sip->regcallid = NULL;
4537 sip->fd = -1;
4538 sip->processing_input = FALSE;
4541 static void sipe_close(PurpleConnection *gc)
4543 struct sipe_account_data *sip = gc->proto_data;
4545 if (sip) {
4546 /* leave all conversations */
4547 im_session_close_all(sip);
4549 /* unregister */
4550 do_register_exp(sip, 0);
4552 sipe_connection_cleanup(sip);
4553 g_free(sip->sipdomain);
4554 g_free(sip->username);
4555 g_free(sip->password);
4556 g_free(sip->authdomain);
4557 g_free(sip->authuser);
4559 g_free(gc->proto_data);
4560 gc->proto_data = NULL;
4563 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4565 PurpleAccount *acct = purple_connection_get_account(gc);
4566 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4567 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4568 if (conv == NULL)
4569 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4570 purple_conversation_present(conv);
4571 g_free(id);
4574 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4577 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4578 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4581 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4583 PurpleNotifySearchResults *results;
4584 PurpleNotifySearchColumn *column;
4585 xmlnode *searchResults;
4586 xmlnode *mrow;
4587 int match_count = 0;
4588 gboolean more = FALSE;
4589 gchar *secondary;
4591 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4592 if (!searchResults) {
4593 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4594 return FALSE;
4597 results = purple_notify_searchresults_new();
4599 if (results == NULL) {
4600 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4601 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4603 xmlnode_free(searchResults);
4604 return FALSE;
4607 column = purple_notify_searchresults_column_new(_("User Name"));
4608 purple_notify_searchresults_column_add(results, column);
4610 column = purple_notify_searchresults_column_new(_("Name"));
4611 purple_notify_searchresults_column_add(results, column);
4613 column = purple_notify_searchresults_column_new(_("Company"));
4614 purple_notify_searchresults_column_add(results, column);
4616 column = purple_notify_searchresults_column_new(_("Country"));
4617 purple_notify_searchresults_column_add(results, column);
4619 column = purple_notify_searchresults_column_new(_("Email"));
4620 purple_notify_searchresults_column_add(results, column);
4622 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4623 GList *row = NULL;
4625 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4626 row = g_list_append(row, g_strdup(uri_parts[1]));
4627 g_strfreev(uri_parts);
4629 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4630 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4631 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4632 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4634 purple_notify_searchresults_row_add(results, row);
4635 match_count++;
4638 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4639 char *data = xmlnode_get_data_unescaped(mrow);
4640 more = (g_strcasecmp(data, "true") == 0);
4641 g_free(data);
4644 secondary = g_strdup_printf(
4645 dngettext(GETTEXT_PACKAGE,
4646 "Found %d contact%s:",
4647 "Found %d contacts%s:", match_count),
4648 match_count, more ? _(" (more matched your query)") : "");
4650 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4651 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4652 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4654 g_free(secondary);
4655 xmlnode_free(searchResults);
4656 return TRUE;
4659 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4661 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4662 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4663 unsigned i = 0;
4665 do {
4666 PurpleRequestField *field = entries->data;
4667 const char *id = purple_request_field_get_id(field);
4668 const char *value = purple_request_field_string_get_value(field);
4670 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
4672 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
4673 } while ((entries = g_list_next(entries)) != NULL);
4674 attrs[i] = NULL;
4676 if (i > 0) {
4677 gchar *query = g_strjoinv(NULL, attrs);
4678 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
4679 send_soap_request_with_cb(gc->proto_data, body,
4680 (TransCallback) process_search_contact_response, NULL);
4681 g_free(body);
4682 g_free(query);
4685 g_strfreev(attrs);
4688 static void sipe_show_find_contact(PurplePluginAction *action)
4690 PurpleConnection *gc = (PurpleConnection *) action->context;
4691 PurpleRequestFields *fields;
4692 PurpleRequestFieldGroup *group;
4693 PurpleRequestField *field;
4695 fields = purple_request_fields_new();
4696 group = purple_request_field_group_new(NULL);
4697 purple_request_fields_add_group(fields, group);
4699 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4700 purple_request_field_group_add_field(group, field);
4701 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4702 purple_request_field_group_add_field(group, field);
4703 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4704 purple_request_field_group_add_field(group, field);
4705 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4706 purple_request_field_group_add_field(group, field);
4708 purple_request_fields(gc,
4709 _("Search"),
4710 _("Search for a Contact"),
4711 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4712 fields,
4713 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4714 _("_Cancel"), NULL,
4715 purple_connection_get_account(gc), NULL, NULL, gc);
4718 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4720 GList *menu = NULL;
4721 PurplePluginAction *act;
4723 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4724 menu = g_list_prepend(menu, act);
4726 menu = g_list_reverse(menu);
4728 return menu;
4731 static void dummy_permit_deny(PurpleConnection *gc)
4735 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4737 return TRUE;
4741 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4743 return TRUE;
4747 static void sipe_plugin_destroy(PurplePlugin *plugin)
4751 static char *sipe_status_text(PurpleBuddy *buddy)
4753 struct sipe_account_data *sip;
4754 struct sipe_buddy *sbuddy;
4755 char *text = NULL;
4757 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4758 if (sip) //happens on pidgin exit
4760 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4761 if (sbuddy && sbuddy->annotation)
4763 text = g_strdup(sbuddy->annotation);
4767 return text;
4770 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4772 const PurplePresence *presence = purple_buddy_get_presence(buddy);
4773 const PurpleStatus *status = purple_presence_get_active_status(presence);
4774 struct sipe_account_data *sip;
4775 struct sipe_buddy *sbuddy;
4776 char *annotation = NULL;
4778 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4779 if (sip) //happens on pidgin exit
4781 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4782 if (sbuddy)
4784 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
4788 //Layout
4789 if (purple_presence_is_online(presence))
4791 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
4794 if (annotation)
4796 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
4797 g_free(annotation);
4802 static GHashTable *
4803 sipe_get_account_text_table(PurpleAccount *account)
4805 GHashTable *table;
4806 table = g_hash_table_new(g_str_hash, g_str_equal);
4807 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
4808 return table;
4811 static PurpleBuddy *
4812 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
4814 PurpleBuddy *clone;
4815 const gchar *server_alias, *email;
4816 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
4818 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
4820 purple_blist_add_buddy(clone, NULL, group, NULL);
4822 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
4823 if (server_alias) {
4824 purple_blist_server_alias_buddy(clone, server_alias);
4827 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4828 if (email) {
4829 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
4832 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
4833 //for UI to update;
4834 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
4835 return clone;
4838 static void
4839 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
4841 PurpleBuddy *buddy, *b;
4842 PurpleConnection *gc;
4843 PurpleGroup * group = purple_find_group(group_name);
4845 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
4847 buddy = (PurpleBuddy *)node;
4849 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
4850 gc = purple_account_get_connection(buddy->account);
4852 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
4853 if (!b){
4854 b = purple_blist_add_buddy_clone(group, buddy);
4857 sipe_group_buddy(gc, buddy->name, NULL, group_name);
4860 static void
4861 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
4863 const gchar *email;
4864 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
4866 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
4867 if (email)
4869 char *mailto = g_strdup_printf("mailto:%s", email);
4870 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
4871 #ifndef _WIN32
4873 pid_t pid;
4874 char *const parmList[] = {mailto, NULL};
4875 if ((pid = fork()) == -1)
4877 purple_debug_info("sipe", "fork() error\n");
4879 else if (pid == 0)
4881 execvp("xdg-email", parmList);
4882 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
4885 #else
4887 BOOL ret;
4888 _flushall();
4889 errno = 0;
4890 //@TODO resolve env variable %WINDIR% first
4891 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
4892 if (errno)
4894 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
4897 #endif
4899 g_free(mailto);
4901 else
4903 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
4908 * A menu which appear when right-clicking on buddy in contact list.
4910 static GList *
4911 sipe_buddy_menu(PurpleBuddy *buddy)
4913 PurpleBlistNode *g_node;
4914 PurpleGroup *group, *gr_parent;
4915 PurpleMenuAction *act;
4916 GList *menu = NULL;
4917 GList *menu_groups = NULL;
4919 act = purple_menu_action_new(_("Send Email..."),
4920 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
4921 NULL, NULL);
4922 menu = g_list_prepend(menu, act);
4924 gr_parent = purple_buddy_get_group(buddy);
4925 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
4926 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
4927 continue;
4929 group = (PurpleGroup *)g_node;
4930 if (group == gr_parent)
4931 continue;
4933 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
4934 continue;
4936 act = purple_menu_action_new(purple_group_get_name(group),
4937 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
4938 group->name, NULL);
4939 menu_groups = g_list_prepend(menu_groups, act);
4941 menu_groups = g_list_reverse(menu_groups);
4943 act = purple_menu_action_new(_("Copy to"),
4944 NULL,
4945 NULL, menu_groups);
4946 menu = g_list_prepend(menu, act);
4947 menu = g_list_reverse(menu);
4949 return menu;
4952 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
4953 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4954 return sipe_buddy_menu((PurpleBuddy *) node);
4955 } else {
4956 return NULL;
4960 static void sipe_get_info(PurpleConnection *gc, const char *username) {
4961 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
4962 PurpleBuddy *pbuddy;
4963 struct sipe_account_data *sip;
4964 struct sipe_buddy *sbuddy;
4965 const char *email;
4966 const char *device_name = NULL;
4967 const char *alias;
4968 const char *server_alias;
4970 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username,
4971 gc->account->username);
4973 pbuddy = purple_find_buddy(gc->account, username);
4974 alias = purple_buddy_get_local_alias(pbuddy);
4975 server_alias = purple_buddy_get_server_alias(pbuddy);
4977 sip = (struct sipe_account_data *)gc->proto_data;
4978 if (sip)
4980 sbuddy = g_hash_table_lookup(sip->buddies, username);
4981 if (sbuddy)
4983 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
4987 //Layout
4988 if (server_alias)
4990 purple_notify_user_info_add_pair(info, _("Nickname"), server_alias);
4993 // same as server alias, do not present
4994 alias = alias && server_alias && !strcmp(alias, server_alias) ? NULL : alias;
4995 if (alias)
4997 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5000 email = purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email");
5001 if (email)
5003 purple_notify_user_info_add_pair(info, _("Email"), email);
5006 if (device_name)
5008 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5011 /* show a buddy's user info in a nice dialog box */
5012 purple_notify_userinfo(gc, /* connection the buddy info came through */
5013 username, /* buddy's username */
5014 info, /* body */
5015 NULL, /* callback called when dialog closed */
5016 NULL); /* userdata for callback */
5019 static PurplePlugin *my_protocol = NULL;
5021 static PurplePluginProtocolInfo prpl_info =
5024 NULL, /* user_splits */
5025 NULL, /* protocol_options */
5026 NO_BUDDY_ICONS, /* icon_spec */
5027 sipe_list_icon, /* list_icon */
5028 NULL, /* list_emblems */
5029 sipe_status_text, /* status_text */
5030 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5031 sipe_status_types, /* away_states */
5032 sipe_blist_node_menu, /* blist_node_menu */
5033 NULL, /* chat_info */
5034 NULL, /* chat_info_defaults */
5035 sipe_login, /* login */
5036 sipe_close, /* close */
5037 sipe_im_send, /* send_im */
5038 NULL, /* set_info */ // TODO maybe
5039 sipe_send_typing, /* send_typing */
5040 sipe_get_info, /* get_info */
5041 sipe_set_status, /* set_status */
5042 NULL, /* set_idle */
5043 NULL, /* change_passwd */
5044 sipe_add_buddy, /* add_buddy */
5045 NULL, /* add_buddies */
5046 sipe_remove_buddy, /* remove_buddy */
5047 NULL, /* remove_buddies */
5048 sipe_add_permit, /* add_permit */
5049 sipe_add_deny, /* add_deny */
5050 sipe_add_deny, /* rem_permit */
5051 sipe_add_permit, /* rem_deny */
5052 dummy_permit_deny, /* set_permit_deny */
5053 NULL, /* join_chat */
5054 NULL, /* reject_chat */
5055 NULL, /* get_chat_name */
5056 NULL, /* chat_invite */
5057 NULL, /* chat_leave */
5058 NULL, /* chat_whisper */
5059 NULL, /* chat_send */
5060 sipe_keep_alive, /* keepalive */
5061 NULL, /* register_user */
5062 NULL, /* get_cb_info */ // deprecated
5063 NULL, /* get_cb_away */ // deprecated
5064 sipe_alias_buddy, /* alias_buddy */
5065 sipe_group_buddy, /* group_buddy */
5066 sipe_rename_group, /* rename_group */
5067 NULL, /* buddy_free */
5068 sipe_convo_closed, /* convo_closed */
5069 purple_normalize_nocase, /* normalize */
5070 NULL, /* set_buddy_icon */
5071 sipe_remove_group, /* remove_group */
5072 NULL, /* get_cb_real_name */ // TODO?
5073 NULL, /* set_chat_topic */
5074 NULL, /* find_blist_chat */
5075 NULL, /* roomlist_get_list */
5076 NULL, /* roomlist_cancel */
5077 NULL, /* roomlist_expand_category */
5078 NULL, /* can_receive_file */
5079 NULL, /* send_file */
5080 NULL, /* new_xfer */
5081 NULL, /* offline_message */
5082 NULL, /* whiteboard_prpl_ops */
5083 sipe_send_raw, /* send_raw */
5084 NULL, /* roomlist_room_serialize */
5085 NULL, /* unregister_user */
5086 NULL, /* send_attention */
5087 NULL, /* get_attention_types */
5089 sizeof(PurplePluginProtocolInfo), /* struct_size */
5090 sipe_get_account_text_table, /* get_account_text_table */
5094 static PurplePluginInfo info = {
5095 PURPLE_PLUGIN_MAGIC,
5096 PURPLE_MAJOR_VERSION,
5097 PURPLE_MINOR_VERSION,
5098 PURPLE_PLUGIN_PROTOCOL, /**< type */
5099 NULL, /**< ui_requirement */
5100 0, /**< flags */
5101 NULL, /**< dependencies */
5102 PURPLE_PRIORITY_DEFAULT, /**< priority */
5103 "prpl-sipe", /**< id */
5104 "Microsoft LCS/OCS", /**< name */
5105 VERSION, /**< version */
5106 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5107 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5108 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5109 "Gabriel Burt <gburt@novell.com>", /**< author */
5110 PURPLE_WEBSITE, /**< homepage */
5111 sipe_plugin_load, /**< load */
5112 sipe_plugin_unload, /**< unload */
5113 sipe_plugin_destroy, /**< destroy */
5114 NULL, /**< ui_info */
5115 &prpl_info, /**< extra_info */
5116 NULL,
5117 sipe_actions,
5118 NULL,
5119 NULL,
5120 NULL,
5121 NULL
5124 static void init_plugin(PurplePlugin *plugin)
5126 PurpleAccountUserSplit *split;
5127 PurpleAccountOption *option;
5129 #ifdef ENABLE_NLS
5130 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5131 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5132 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5133 #endif
5135 purple_plugin_register(plugin);
5137 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5138 purple_account_user_split_set_reverse(split, FALSE);
5139 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5141 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5142 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5143 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5144 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5146 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5147 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5148 // Translators: noun (networking port)
5149 option = purple_account_option_int_new(_("Port"), "port", 5061);
5150 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5152 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5153 purple_account_option_add_list_item(option, _("Auto"), "auto");
5154 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5155 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5156 purple_account_option_add_list_item(option, _("UDP"), "udp");
5157 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5159 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5160 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5162 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5163 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5165 // TODO commented out so won't show in the preferences until we fix krb message signing
5166 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5167 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5169 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5170 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5171 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5174 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5175 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5176 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5177 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5178 my_protocol = plugin;
5181 /* I had to redefined the function for it load, but works */
5182 gboolean purple_init_plugin(PurplePlugin *plugin){
5183 plugin->info = &(info);
5184 init_plugin((plugin));
5185 sipe_plugin_load((plugin));
5186 return purple_plugin_register(plugin);