Send presence subscribe requests to poolFqdn servers
[siplcs.git] / src / sipe.c
blob13191a3766b28c462b4e6d939bc85947df9af72e
1 /**
2 * @file sipe.c
4 * pidgin-sipe
5 *
6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
11 * ***
12 * Thanks to Google's Summer of Code Program and the helpful mentors
13 * ***
15 * Session-based SIP MESSAGE documentation:
16 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #ifndef _WIN32
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <netinet/in.h>
38 #include <net/if.h>
39 #ifdef ENABLE_NLS
40 # include <libintl.h>
41 # define _(String) ((const char *) gettext (String))
42 #else
43 # define _(String) ((const char *) (String))
44 #endif /* ENABLE_NLS */
45 #else
46 #ifdef _DLL
47 #define _WS2TCPIP_H_
48 #define _WINSOCK2API_
49 #define _LIBC_INTERNAL_
50 #endif /* _DLL */
52 #include "internal.h"
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <string.h>
59 #include <glib.h>
62 #include "accountopt.h"
63 #include "blist.h"
64 #include "conversation.h"
65 #include "dnsquery.h"
66 #include "debug.h"
67 #include "notify.h"
68 #include "privacy.h"
69 #include "prpl.h"
70 #include "plugin.h"
71 #include "util.h"
72 #include "version.h"
73 #include "network.h"
74 #include "xmlnode.h"
75 #include "mime.h"
77 #include "sipe.h"
78 #include "sip-sec.h"
79 #include "sip-ntlm.h"
80 #ifdef USE_KERBEROS
81 #include "sipkrb5.h"
82 #endif /*USE_KERBEROS*/
84 #include "sipmsg.h"
85 #include "sipe-sign.h"
86 #include "dnssrv.h"
87 #include "request.h"
89 /* Keep in sync with sipe_transport_type! */
90 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
91 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
93 /* Status identifiers (see also: sipe_status_types()) */
94 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
95 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
96 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
97 /* PURPLE_STATUS_UNAVAILABLE: */
98 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
99 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
100 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
101 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
102 /* PURPLE_STATUS_AWAY: */
103 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
104 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
105 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
106 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
107 /* ??? PURPLE_STATUS_MOBILE */
108 /* ??? PURPLE_STATUS_TUNE */
110 /* Action name templates */
111 #define ACTION_NAME_PRESENCE "<presence><%s>"
113 static char *gentag()
115 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
118 static gchar *get_epid(struct sipe_account_data *sip)
120 if (!sip->epid) {
121 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
123 return g_strdup(sip->epid);
126 static char *genbranch()
128 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
129 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
130 rand() & 0xFFFF, rand() & 0xFFFF);
133 static char *gencallid()
135 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
136 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
137 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
138 rand() & 0xFFFF, rand() & 0xFFFF);
141 static gchar *find_tag(const gchar *hdr)
143 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
144 if (!tag) {
145 // In case it's at the end and there's no trailing ;
146 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
148 return tag;
152 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
154 return "sipe";
157 static void sipe_plugin_destroy(PurplePlugin *plugin);
159 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
161 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
162 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
163 gpointer data);
165 static void sipe_close(PurpleConnection *gc);
167 static void send_presence_status(struct sipe_account_data *sip);
169 static void sendout_pkt(PurpleConnection *gc, const char *buf);
171 static void sipe_keep_alive(PurpleConnection *gc)
173 struct sipe_account_data *sip = gc->proto_data;
174 if (sip->transport == SIPE_TRANSPORT_UDP) {
175 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
176 gchar buf[2] = {0, 0};
177 purple_debug_info("sipe", "sending keep alive\n");
178 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
179 } else {
180 time_t now = time(NULL);
181 if ((sip->keepalive_timeout > 0) &&
182 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
183 #if PURPLE_VERSION_CHECK(2,4,0)
184 && ((now - gc->last_received) >= sip->keepalive_timeout)
185 #endif
187 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
188 sendout_pkt(gc, "\r\n\r\n");
189 sip->last_keepalive = now;
194 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
196 struct sip_connection *ret = NULL;
197 GSList *entry = sip->openconns;
198 while (entry) {
199 ret = entry->data;
200 if (ret->fd == fd) return ret;
201 entry = entry->next;
203 return NULL;
206 static void sipe_auth_free(struct sip_auth *auth)
208 g_free(auth->nonce);
209 auth->nonce = NULL;
210 g_free(auth->opaque);
211 auth->opaque = NULL;
212 g_free(auth->realm);
213 auth->realm = NULL;
214 g_free(auth->target);
215 auth->target = NULL;
216 g_free(auth->digest_session_key);
217 auth->digest_session_key = NULL;
218 auth->type = AUTH_TYPE_UNSET;
219 auth->retries = 0;
220 auth->expires = 0;
221 g_free(auth->gssapi_data);
222 auth->gssapi_data = NULL;
223 sip_sec_destroy_context(auth->gssapi_context);
224 auth->gssapi_context = NULL;
227 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
229 struct sip_connection *ret = g_new0(struct sip_connection, 1);
230 ret->fd = fd;
231 sip->openconns = g_slist_append(sip->openconns, ret);
232 return ret;
235 static void connection_remove(struct sipe_account_data *sip, int fd)
237 struct sip_connection *conn = connection_find(sip, fd);
238 if (conn) {
239 sip->openconns = g_slist_remove(sip->openconns, conn);
240 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
241 g_free(conn->inbuf);
242 g_free(conn);
246 static void connection_free_all(struct sipe_account_data *sip)
248 struct sip_connection *ret = NULL;
249 GSList *entry = sip->openconns;
250 while (entry) {
251 ret = entry->data;
252 connection_remove(sip, ret->fd);
253 entry = sip->openconns;
257 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
259 const gchar *method = msg->method;
260 const gchar *target = msg->target;
261 gchar noncecount[9];
262 gchar *response;
263 gchar *ret;
264 gchar *tmp = NULL;
265 const char *authdomain = sip->authdomain;
266 const char *authuser = sip->authuser;
267 //const char *krb5_realm;
268 const char *host;
269 //gchar *krb5_token = NULL;
271 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
272 // and do error checking
274 // KRB realm should always be uppercase
275 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
277 if (sip->realhostname) {
278 host = sip->realhostname;
279 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
280 host = purple_account_get_string(sip->account, "proxy", "");
281 } else {
282 host = sip->sipdomain;
285 /*gboolean new_auth = krb5_auth.gss_context == NULL;
286 if (new_auth) {
287 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
290 if (new_auth || force_reauth) {
291 krb5_token = krb5_auth.base64_token;
294 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
295 krb5_token = krb5_auth.base64_token;*/
297 if (!authdomain) {
298 authdomain = "";
301 if (!authuser || strlen(authuser) < 1) {
302 authuser = sip->username;
305 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
306 sprintf(noncecount, "%08d", auth->nc++);
307 response = purple_cipher_http_digest_calculate_response(
308 "md5", method, target, NULL, NULL,
309 auth->nonce, noncecount, NULL, auth->digest_session_key);
310 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
312 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);
313 g_free(response);
314 return ret;
315 } else if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM */ /* Kerberos */
316 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
317 // If we have a signature for the message, include that
318 if (msg->signature) {
319 tmp = g_strdup_printf("%s qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth_protocol, auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
320 return tmp;
323 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
324 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
325 gchar *gssapi_data;
326 gchar *opaque;
328 gssapi_data = sip_sec_init_context(&(auth->gssapi_context), auth_protocol,
329 authdomain, authuser, sip->password,
330 auth->target,
331 auth->gssapi_data);
333 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
334 tmp = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
335 g_free(opaque);
336 g_free(gssapi_data);
337 return tmp;
340 tmp = g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
341 return tmp;
344 sprintf(noncecount, "%08d", auth->nc++);
345 response = purple_cipher_http_digest_calculate_response(
346 "md5", method, target, NULL, NULL,
347 auth->nonce, noncecount, NULL, auth->digest_session_key);
348 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
350 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);
351 g_free(response);
352 return ret;
355 static char *parse_attribute(const char *attrname, const char *source)
357 const char *tmp, *tmp2;
358 char *retval = NULL;
359 int len = strlen(attrname);
361 if (!strncmp(source, attrname, len)) {
362 tmp = source + len;
363 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
364 if (tmp2)
365 retval = g_strndup(tmp, tmp2 - tmp);
366 else
367 retval = g_strdup(tmp);
370 return retval;
373 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
375 int i = 0;
376 const char *authuser;
377 char *tmp;
378 gchar **parts;
379 //const char *krb5_realm;
380 //const char *host;
382 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
383 // and do error checking
385 // KRB realm should always be uppercase
386 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
388 if (sip->realhostname) {
389 host = sip->realhostname;
390 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
391 host = purple_account_get_string(sip->account, "proxy", "");
392 } else {
393 host = sip->sipdomain;
396 authuser = sip->authuser;
398 if (!authuser || strlen(authuser) < 1) {
399 authuser = sip->username;
402 if (!hdr) {
403 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
404 return;
407 if (!g_strncasecmp(hdr, "NTLM", 4)) {
408 auth->type = AUTH_TYPE_NTLM;
409 parts = g_strsplit(hdr+5, "\", ", 0);
410 i = 0;
411 while (parts[i]) {
412 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
414 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
415 g_free(auth->nonce);
416 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
418 g_free(auth->gssapi_data);
419 auth->gssapi_data = tmp;
421 if ((tmp = parse_attribute("targetname=\"",
422 parts[i]))) {
423 g_free(auth->target);
424 auth->target = tmp;
426 else if ((tmp = parse_attribute("realm=\"",
427 parts[i]))) {
428 g_free(auth->realm);
429 auth->realm = tmp;
431 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
432 g_free(auth->opaque);
433 auth->opaque = tmp;
435 i++;
437 g_strfreev(parts);
438 auth->nc = 1;
439 if (!strstr(hdr, "gssapi-data")) {
440 auth->nc = 1;
441 } else {
442 auth->nc = 3;
444 return;
447 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
448 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
449 auth->type = AUTH_TYPE_KERBEROS;
450 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
451 parts = g_strsplit(hdr+9, "\", ", 0);
452 i = 0;
453 while (parts[i]) {
454 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
455 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
456 /*if (krb5_auth.gss_context == NULL) {
457 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
459 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
461 g_free(auth->gssapi_data);
462 auth->gssapi_data = tmp;
464 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
465 g_free(auth->target);
466 auth->target = tmp;
467 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
468 g_free(auth->realm);
469 auth->realm = tmp;
470 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
471 g_free(auth->opaque);
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 g_free(auth->nonce);
486 auth->nonce = tmp;
488 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
489 g_free(auth->realm);
490 auth->realm = tmp;
492 i++;
494 g_strfreev(parts);
496 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
497 if (auth->realm) {
498 g_free(auth->digest_session_key);
499 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
500 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
502 auth->nc = 1;
506 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
508 PurpleConnection *gc = data;
509 struct sipe_account_data *sip = gc->proto_data;
510 gsize max_write;
511 gssize written;
513 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
515 if (max_write == 0) {
516 if (sip->tx_handler != 0){
517 purple_input_remove(sip->tx_handler);
518 sip->tx_handler = 0;
520 return;
523 written = write(sip->fd, sip->txbuf->outptr, max_write);
525 if (written < 0 && errno == EAGAIN)
526 written = 0;
527 else if (written <= 0) {
528 /*TODO: do we really want to disconnect on a failure to write?*/
529 purple_connection_error(gc, _("Could not write"));
530 return;
533 purple_circ_buffer_mark_read(sip->txbuf, written);
536 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
538 PurpleConnection *gc = data;
539 struct sipe_account_data *sip = gc->proto_data;
540 gsize max_write;
541 gssize written;
543 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
545 if (max_write == 0) {
546 if (sip->tx_handler != 0) {
547 purple_input_remove(sip->tx_handler);
548 sip->tx_handler = 0;
549 return;
553 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
555 if (written < 0 && errno == EAGAIN)
556 written = 0;
557 else if (written <= 0) {
558 /*TODO: do we really want to disconnect on a failure to write?*/
559 purple_connection_error(gc, _("Could not write"));
560 return;
563 purple_circ_buffer_mark_read(sip->txbuf, written);
566 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
568 static void send_later_cb(gpointer data, gint source, const gchar *error)
570 PurpleConnection *gc = data;
571 struct sipe_account_data *sip;
572 struct sip_connection *conn;
574 if (!PURPLE_CONNECTION_IS_VALID(gc))
576 if (source >= 0)
577 close(source);
578 return;
581 if (source < 0) {
582 purple_connection_error(gc, _("Could not connect"));
583 return;
586 sip = gc->proto_data;
587 sip->fd = source;
588 sip->connecting = FALSE;
589 sip->last_keepalive = time(NULL);
591 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
593 /* If there is more to write now, we need to register a handler */
594 if (sip->txbuf->bufused > 0)
595 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
597 conn = connection_create(sip, source);
598 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
601 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
603 struct sipe_account_data *sip;
604 struct sip_connection *conn;
606 if (!PURPLE_CONNECTION_IS_VALID(gc))
608 if (gsc) purple_ssl_close(gsc);
609 return NULL;
612 sip = gc->proto_data;
613 sip->fd = gsc->fd;
614 sip->gsc = gsc;
615 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
616 sip->connecting = FALSE;
617 sip->last_keepalive = time(NULL);
619 conn = connection_create(sip, gsc->fd);
621 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
623 return sip;
626 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
628 PurpleConnection *gc = data;
629 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
630 if (sip == NULL) return;
632 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
634 /* If there is more to write now */
635 if (sip->txbuf->bufused > 0) {
636 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
641 static void sendlater(PurpleConnection *gc, const char *buf)
643 struct sipe_account_data *sip = gc->proto_data;
645 if (!sip->connecting) {
646 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
647 if (sip->transport == SIPE_TRANSPORT_TLS){
648 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
649 } else {
650 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
651 purple_connection_error(gc, _("Couldn't create socket"));
654 sip->connecting = TRUE;
657 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
658 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
660 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
663 static void sendout_pkt(PurpleConnection *gc, const char *buf)
665 struct sipe_account_data *sip = gc->proto_data;
666 time_t currtime = time(NULL);
667 int writelen = strlen(buf);
669 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
670 if (sip->transport == SIPE_TRANSPORT_UDP) {
671 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
672 purple_debug_info("sipe", "could not send packet\n");
674 } else {
675 int ret;
676 if (sip->fd < 0) {
677 sendlater(gc, buf);
678 return;
681 if (sip->tx_handler) {
682 ret = -1;
683 errno = EAGAIN;
684 } else{
685 if (sip->gsc){
686 ret = purple_ssl_write(sip->gsc, buf, writelen);
687 }else{
688 ret = write(sip->fd, buf, writelen);
692 if (ret < 0 && errno == EAGAIN)
693 ret = 0;
694 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
695 sendlater(gc, buf);
696 return;
699 if (ret < writelen) {
700 if (!sip->tx_handler){
701 if (sip->gsc){
702 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
704 else{
705 sip->tx_handler = purple_input_add(sip->fd,
706 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
707 gc);
711 /* XXX: is it OK to do this? You might get part of a request sent
712 with part of another. */
713 if (sip->txbuf->bufused > 0)
714 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
716 purple_circ_buffer_append(sip->txbuf, buf + ret,
717 writelen - ret);
722 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
724 sendout_pkt(gc, buf);
725 return len;
728 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
730 GSList *tmp = msg->headers;
731 gchar *name;
732 gchar *value;
733 GString *outstr = g_string_new("");
734 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
735 while (tmp) {
736 name = ((struct siphdrelement*) (tmp->data))->name;
737 value = ((struct siphdrelement*) (tmp->data))->value;
738 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
739 tmp = g_slist_next(tmp);
741 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
742 sendout_pkt(sip->gc, outstr->str);
743 g_string_free(outstr, TRUE);
746 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
748 gchar * buf;
749 if (sip->registrar.gssapi_context) {
750 struct sipmsg_breakdown msgbd;
751 gchar *signature_input_str;
752 msgbd.msg = msg;
753 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
754 msgbd.rand = g_strdup_printf("%08x", g_random_int());
755 sip->registrar.ntlm_num++;
756 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
757 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
758 if (signature_input_str != NULL) {
759 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
760 msg->signature = signature_hex;
761 msg->rand = g_strdup(msgbd.rand);
762 msg->num = g_strdup(msgbd.num);
763 g_free(signature_input_str);
765 sipmsg_breakdown_free(&msgbd);
768 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
769 buf = auth_header(sip, &sip->registrar, msg);
770 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
771 sipmsg_add_header(msg, "Authorization", buf);
772 } else {
773 sipmsg_add_header_pos(msg, "Authorization", buf, 5); // What's the point in 5?
775 g_free(buf);
776 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS")) {
777 sip->registrar.nc = 3;
778 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
779 sip->registrar.type = AUTH_TYPE_NTLM;
780 } else {
781 sip->registrar.type = AUTH_TYPE_KERBEROS;
785 buf = auth_header(sip, &sip->registrar, msg);
786 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
787 g_free(buf);
788 } else {
789 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
793 static char *get_contact(struct sipe_account_data *sip)
795 return g_strdup(sip->contact);
799 * unused. Needed?
800 static char *get_contact_service(struct sipe_account_data *sip)
802 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()));
803 //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);
807 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
808 const char *text, const char *body)
810 gchar *name;
811 gchar *value;
812 GString *outstr = g_string_new("");
813 struct sipe_account_data *sip = gc->proto_data;
814 gchar *contact;
815 GSList *tmp;
817 sipmsg_remove_header(msg, "ms-user-data");
819 contact = get_contact(sip);
820 sipmsg_remove_header(msg, "Contact");
821 sipmsg_add_header(msg, "Contact", contact);
822 g_free(contact);
824 /* When sending the acknowlegements and errors, the content length from the original
825 message is still here, but there is no body; we need to make sure we're sending the
826 correct content length */
827 sipmsg_remove_header(msg, "Content-Length");
828 if (body) {
829 gchar len[12];
830 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
831 sipmsg_add_header(msg, "Content-Length", len);
832 } else {
833 sipmsg_remove_header(msg, "Content-Type");
834 sipmsg_add_header(msg, "Content-Length", "0");
837 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
838 //gchar * mic = "MICTODO";
839 msg->response = code;
841 sipmsg_remove_header(msg, "Authentication-Info");
842 sign_outgoing_message(msg, sip, msg->method);
844 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
845 tmp = msg->headers;
846 while (tmp) {
847 name = ((struct siphdrelement*) (tmp->data))->name;
848 value = ((struct siphdrelement*) (tmp->data))->value;
850 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
851 tmp = g_slist_next(tmp);
853 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
854 sendout_pkt(gc, outstr->str);
855 g_string_free(outstr, TRUE);
858 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
860 if (trans->msg) sipmsg_free(trans->msg);
861 sip->transactions = g_slist_remove(sip->transactions, trans);
862 g_free(trans);
865 static struct transaction *
866 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
868 struct transaction *trans = g_new0(struct transaction, 1);
869 trans->time = time(NULL);
870 trans->msg = (struct sipmsg *)msg;
871 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
872 trans->callback = callback;
873 sip->transactions = g_slist_append(sip->transactions, trans);
874 return trans;
877 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
879 struct transaction *trans;
880 GSList *transactions = sip->transactions;
881 gchar *cseq = sipmsg_find_header(msg, "CSeq");
883 while (transactions) {
884 trans = transactions->data;
885 if (!strcmp(trans->cseq, cseq)) {
886 return trans;
888 transactions = transactions->next;
891 return NULL;
894 static struct transaction *
895 send_sip_request(PurpleConnection *gc, const gchar *method,
896 const gchar *url, const gchar *to, const gchar *addheaders,
897 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
899 struct sipe_account_data *sip = gc->proto_data;
900 const char *addh = "";
901 char *buf;
902 struct sipmsg *msg;
903 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
904 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
905 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
906 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
907 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
908 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
909 gchar *route = strdup("");
910 gchar *epid = get_epid(sip); // TODO generate one per account/login
911 struct transaction *trans;
913 if (dialog && dialog->routes)
915 GSList *iter = dialog->routes;
917 while(iter)
919 char *tmp = route;
920 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
921 g_free(tmp);
922 iter = g_slist_next(iter);
926 if (!ourtag && !dialog) {
927 ourtag = gentag();
930 if (!strcmp(method, "REGISTER")) {
931 if (sip->regcallid) {
932 g_free(callid);
933 callid = g_strdup(sip->regcallid);
934 } else {
935 sip->regcallid = g_strdup(callid);
939 if (addheaders) addh = addheaders;
941 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
942 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
943 "From: <sip:%s>%s%s;epid=%s\r\n"
944 "To: <%s>%s%s%s%s\r\n"
945 "Max-Forwards: 70\r\n"
946 "CSeq: %d %s\r\n"
947 "User-Agent: %s\r\n"
948 "Call-ID: %s\r\n"
949 "%s%s"
950 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
951 method,
952 dialog && dialog->request ? dialog->request : url,
953 TRANSPORT_DESCRIPTOR,
954 purple_network_get_my_ip(-1),
955 sip->listenport,
956 branch ? ";branch=" : "",
957 branch ? branch : "",
958 sip->username,
959 ourtag ? ";tag=" : "",
960 ourtag ? ourtag : "",
961 epid,
963 theirtag ? ";tag=" : "",
964 theirtag ? theirtag : "",
965 theirepid ? ";epid=" : "",
966 theirepid ? theirepid : "",
967 dialog ? ++dialog->cseq : ++sip->cseq,
968 method,
969 useragent,
970 callid,
971 route,
972 addh,
973 body ? strlen(body) : 0,
974 body ? body : "");
977 //printf ("parsing msg buf:\n%s\n\n", buf);
978 msg = sipmsg_parse_msg(buf);
980 g_free(buf);
981 g_free(ourtag);
982 g_free(theirtag);
983 g_free(theirepid);
984 g_free(branch);
985 g_free(callid);
986 g_free(route);
987 g_free(epid);
989 sign_outgoing_message (msg, sip, method);
991 buf = sipmsg_to_string (msg);
993 /* add to ongoing transactions */
994 trans = transactions_add_buf(sip, msg, tc);
995 sendout_pkt(gc, buf);
996 g_free(buf);
998 return trans;
1001 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1003 gchar *from = g_strdup_printf("sip:%s", sip->username);
1004 gchar *contact = get_contact(sip);
1005 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1006 "Content-Type: application/SOAP+xml\r\n",contact);
1008 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1009 tr->payload = payload;
1011 g_free(from);
1012 g_free(contact);
1013 g_free(hdr);
1016 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1018 send_soap_request_with_cb(sip, body, NULL, NULL);
1021 static char *get_contact_register(struct sipe_account_data *sip)
1023 char *epid = get_epid(sip);
1024 char *uuid = generateUUIDfromEPID(epid);
1025 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1026 g_free(uuid);
1027 g_free(epid);
1028 return(buf);
1031 static void do_register_exp(struct sipe_account_data *sip, int expire)
1033 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1034 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1035 char *to = g_strdup_printf("sip:%s", sip->username);
1036 char *contact = get_contact_register(sip);
1037 char *hdr = g_strdup_printf("Contact: %s\r\n"
1038 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1039 "Event: registration\r\n"
1040 "Allow-Events: presence\r\n"
1041 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1042 "%s", contact, expires);
1043 g_free(contact);
1044 g_free(expires);
1046 sip->registerstatus = 1;
1048 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1049 process_register_response);
1051 g_free(hdr);
1052 g_free(uri);
1053 g_free(to);
1056 static void do_register_cb(struct sipe_account_data *sip, void *unused)
1058 do_register_exp(sip, -1);
1059 sip->reregister_set = FALSE;
1062 static void do_register(struct sipe_account_data *sip)
1064 do_register_exp(sip, -1);
1068 * Returns URI from provided To or From header.
1070 * Needs to g_free() after use.
1072 * @return URI with sip: prefix
1074 static gchar *parse_from(const gchar *hdr)
1076 gchar *from;
1077 const gchar *tmp, *tmp2 = hdr;
1079 if (!hdr) return NULL;
1080 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1081 tmp = strchr(hdr, '<');
1083 /* i hate the different SIP UA behaviours... */
1084 if (tmp) { /* sip address in <...> */
1085 tmp2 = tmp + 1;
1086 tmp = strchr(tmp2, '>');
1087 if (tmp) {
1088 from = g_strndup(tmp2, tmp - tmp2);
1089 } else {
1090 purple_debug_info("sipe", "found < without > in From\n");
1091 return NULL;
1093 } else {
1094 tmp = strchr(tmp2, ';');
1095 if (tmp) {
1096 from = g_strndup(tmp2, tmp - tmp2);
1097 } else {
1098 from = g_strdup(tmp2);
1101 purple_debug_info("sipe", "got %s\n", from);
1102 return from;
1105 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1107 va_list args;
1108 xmlnode * node = NULL;
1109 const gchar * name;
1111 va_start(args, parent);
1112 while ((name = va_arg(args, const char *)) != NULL) {
1113 node = xmlnode_get_child(parent, name);
1114 if (node == NULL) return NULL;
1115 parent = node;
1117 va_end(args);
1119 return node;
1123 static void
1124 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1126 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1127 send_soap_request(sip, body);
1128 g_free(body);
1131 static void
1132 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1134 if (allow) {
1135 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1136 } else {
1137 purple_debug_info("sipe", "Blocking contact %s\n", who);
1140 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1143 static
1144 void sipe_auth_user_cb(void * data)
1146 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1147 if (!job) return;
1149 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1150 g_free(job);
1153 static
1154 void sipe_deny_user_cb(void * data)
1156 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1157 if (!job) return;
1159 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1160 g_free(job);
1163 static void
1164 sipe_add_permit(PurpleConnection *gc, const char *name)
1166 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1167 sipe_contact_allow_deny(sip, name, TRUE);
1170 static void
1171 sipe_add_deny(PurpleConnection *gc, const char *name)
1173 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1174 sipe_contact_allow_deny(sip, name, FALSE);
1177 /*static void
1178 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1180 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1181 sipe_contact_set_acl(sip, name, "");
1184 static void
1185 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1187 xmlnode *watchers;
1188 xmlnode *watcher;
1189 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1190 if (msg->response != 0 && msg->response != 200) return;
1192 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1194 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1195 if (!watchers) return;
1197 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1198 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1199 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1200 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1202 // TODO pull out optional displayName to pass as alias
1203 if (remote_user) {
1204 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1205 job->who = remote_user;
1206 job->sip = sip;
1207 purple_account_request_authorization(
1208 sip->account,
1209 remote_user,
1210 NULL, // id
1211 alias,
1212 NULL, // message
1213 on_list,
1214 sipe_auth_user_cb,
1215 sipe_deny_user_cb,
1216 (void *) job);
1221 xmlnode_free(watchers);
1222 return;
1225 static void
1226 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1228 PurpleGroup * purple_group = purple_find_group(group->name);
1229 if (!purple_group) {
1230 purple_group = purple_group_new(group->name);
1231 purple_blist_add_group(purple_group, NULL);
1234 if (purple_group) {
1235 group->purple_group = purple_group;
1236 sip->groups = g_slist_append(sip->groups, group);
1237 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1238 } else {
1239 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1243 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1245 struct sipe_group *group;
1246 GSList *entry;
1247 if (sip == NULL) {
1248 return NULL;
1251 entry = sip->groups;
1252 while (entry) {
1253 group = entry->data;
1254 if (group->id == id) {
1255 return group;
1257 entry = entry->next;
1259 return NULL;
1262 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1264 struct sipe_group *group;
1265 GSList *entry;
1266 if (sip == NULL) {
1267 return NULL;
1270 entry = sip->groups;
1271 while (entry) {
1272 group = entry->data;
1273 if (!strcmp(group->name, name)) {
1274 return group;
1276 entry = entry->next;
1278 return NULL;
1281 static void
1282 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1284 gchar *body;
1285 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1286 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1287 send_soap_request(sip, body);
1288 g_free(body);
1289 g_free(group->name);
1290 group->name = g_strdup(name);
1294 * Only appends if no such value already stored.
1295 * Like Set in Java.
1297 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1298 GSList * res = list;
1299 if (!g_slist_find_custom(list, data, func)) {
1300 res = g_slist_insert_sorted(list, data, func);
1302 return res;
1305 static int
1306 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1307 return group1->id - group2->id;
1311 * Returns string like "2 4 7 8" - group ids buddy belong to.
1313 static gchar *
1314 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1315 int i = 0;
1316 gchar *res;
1317 //creating array from GList, converting int to gchar*
1318 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1319 GSList *entry = buddy->groups;
1320 while (entry) {
1321 struct sipe_group * group = entry->data;
1322 ids_arr[i] = g_strdup_printf("%d", group->id);
1323 entry = entry->next;
1324 i++;
1326 ids_arr[i] = NULL;
1327 res = g_strjoinv(" ", ids_arr);
1328 g_strfreev(ids_arr);
1329 return res;
1333 * Sends buddy update to server
1335 static void
1336 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1338 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1339 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1341 if (buddy && purple_buddy) {
1342 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1343 gchar *body;
1344 gchar *groups = sipe_get_buddy_groups_string(buddy);
1345 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1347 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1348 alias, groups, "true", buddy->name, sip->contacts_delta++
1350 send_soap_request(sip, body);
1351 g_free(groups);
1352 g_free(body);
1356 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1358 if (msg->response == 200) {
1359 struct sipe_group *group;
1360 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1361 xmlnode *xml;
1362 xmlnode *node;
1363 char *group_id;
1364 struct sipe_buddy *buddy;
1366 xml = xmlnode_from_str(msg->body, msg->bodylen);
1367 if (!xml) {
1368 g_free(ctx);
1369 return FALSE;
1372 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1373 if (!node) {
1374 g_free(ctx);
1375 xmlnode_free(xml);
1376 return FALSE;
1379 group_id = xmlnode_get_data(node);
1380 if (!group_id) {
1381 g_free(ctx);
1382 xmlnode_free(xml);
1383 return FALSE;
1386 group = g_new0(struct sipe_group, 1);
1387 group->id = (int)g_ascii_strtod(group_id, NULL);
1388 g_free(group_id);
1389 group->name = ctx->group_name;
1391 sipe_group_add(sip, group);
1393 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1394 if (buddy) {
1395 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1398 sipe_group_set_user(sip, ctx->user_name);
1400 g_free(ctx);
1401 xmlnode_free(xml);
1402 return TRUE;
1404 return FALSE;
1407 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1409 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1410 gchar *body;
1411 ctx->group_name = g_strdup(name);
1412 ctx->user_name = g_strdup(who);
1414 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1415 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1416 g_free(body);
1420 * Data structure for scheduled actions
1422 typedef void (*Action) (struct sipe_account_data *, void *);
1424 struct scheduled_action {
1425 /**
1426 * Name of action.
1427 * Format is <Event>[<Data>...]
1428 * Example: <presence><sip:user@domain.com> or <registration>
1430 gchar *name;
1431 guint timeout_handler;
1432 gboolean repetitive;
1433 Action action;
1434 GDestroyNotify destroy;
1435 struct sipe_account_data *sip;
1436 void *payload;
1440 * A timer callback
1441 * Should return FALSE if repetitive action is not needed
1443 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1445 gboolean ret;
1446 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1447 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1448 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1449 (sched_action->action)(sched_action->sip, sched_action->payload);
1450 ret = sched_action->repetitive;
1451 (*sched_action->destroy)(sched_action->payload);
1452 g_free(sched_action->name);
1453 g_free(sched_action);
1454 return ret;
1458 * Kills action timer effectively cancelling
1459 * scheduled action
1461 * @param name of action
1463 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1465 GSList *entry;
1467 if (!sip->timeouts || !name) return;
1469 entry = sip->timeouts;
1470 while (entry) {
1471 struct scheduled_action *sched_action = entry->data;
1472 if(!strcmp(sched_action->name, name)) {
1473 GSList *to_delete = entry;
1474 entry = entry->next;
1475 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1476 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1477 purple_timeout_remove(sched_action->timeout_handler);
1478 (*sched_action->destroy)(sched_action->payload);
1479 g_free(sched_action->name);
1480 g_free(sched_action);
1481 } else {
1482 entry = entry->next;
1488 * Do schedule action for execution in the future.
1489 * Non repetitive execution.
1491 * @param name of action (will be copied)
1492 * @param timeout in seconds
1493 * @action callback function
1494 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1496 static void sipe_schedule_action(const gchar *name, int timeout, Action action, GDestroyNotify destroy, struct sipe_account_data *sip, void *payload)
1498 struct scheduled_action *sched_action;
1500 /* Make sure each action only exists once */
1501 sipe_cancel_scheduled_action(sip, name);
1503 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1504 sched_action = g_new0(struct scheduled_action, 1);
1505 sched_action->repetitive = FALSE;
1506 sched_action->name = g_strdup(name);
1507 sched_action->action = action;
1508 sched_action->destroy = destroy ? destroy : g_free;
1509 sched_action->sip = sip;
1510 sched_action->payload = payload;
1511 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1512 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1513 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1516 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1518 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1520 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1522 process_incoming_notify(sip, msg, FALSE, FALSE);
1524 return TRUE;
1527 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1529 gchar *tmp = *resources_uri;
1530 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1531 g_free(tmp);
1534 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1536 gchar *tmp = *resources_uri;
1537 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1538 if(sbuddy){
1539 if(!sbuddy->resubscribed){ //Only not resubscribed contacts; the first time everybody are included
1540 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1543 g_free(tmp);
1547 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1548 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1549 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1550 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1551 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1554 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1556 gchar *contact = get_contact(sip);
1557 gchar *request;
1558 gchar *content;
1559 gchar *require = "";
1560 gchar *accept = "";
1561 gchar *autoextend = "";
1562 gchar *content_type;
1564 if (sip->msrtc_event_categories) {
1565 require = ", categoryList";
1566 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1567 content_type = "application/msrtc-adrl-categorylist+xml";
1568 content = g_strdup_printf(
1569 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1570 "<action name=\"subscribe\" id=\"63792024\">\n"
1571 "<adhocList>\n%s</adhocList>\n"
1572 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1573 "<category name=\"note\"/>\n"
1574 "<category name=\"state\"/>\n"
1575 "</categoryList>\n"
1576 "</action>\n"
1577 "</batchSub>", sip->username, resources_uri);
1578 } else {
1579 autoextend = "Supported: com.microsoft.autoextend\r\n";
1580 content_type = "application/adrl+xml";
1581 content = g_strdup_printf(
1582 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1583 "<create xmlns=\"\">\n%s</create>\n"
1584 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1586 g_free(resources_uri);
1588 request = g_strdup_printf(
1589 "Require: adhoclist%s\r\n"
1590 "Supported: eventlist\r\n"
1591 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1592 "Supported: ms-piggyback-first-notify\r\n"
1593 "%sSupported: ms-benotify\r\n"
1594 "Proxy-Require: ms-benotify\r\n"
1595 "Event: presence\r\n"
1596 "Content-Type: %s\r\n"
1597 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1598 g_free(contact);
1600 /* subscribe to buddy presence */
1601 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1602 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1604 g_free(content);
1605 g_free(to);
1606 g_free(request);
1609 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip, void *unused)
1611 gchar *to = g_strdup_printf("sip:%s", sip->username);
1612 gchar *resources_uri = g_strdup("");
1613 if (sip->msrtc_event_categories) {
1614 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1615 } else {
1616 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1618 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1621 struct presence_batched_routed {
1622 gchar *host;
1623 GSList *buddies;
1626 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1628 struct presence_batched_routed *data = payload;
1629 GSList *buddies = data->buddies;
1630 while (buddies) {
1631 g_free(buddies->data);
1632 buddies = buddies->next;
1634 g_slist_free(data->buddies);
1635 g_free(data->host);
1636 g_free(payload);
1639 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1641 struct presence_batched_routed *data = payload;
1642 GSList *buddies = data->buddies;
1643 gchar *resources_uri = g_strdup("");
1644 while (buddies) {
1645 gchar *tmp = resources_uri;
1646 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, buddies->data);
1647 g_free(tmp);
1648 buddies = buddies->next;
1650 sipe_subscribe_presence_batched_to(sip, resources_uri,
1651 g_strdup(data->host));
1655 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1656 * The user sends a single SUBSCRIBE request to the subscribed contact.
1657 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1661 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1663 gchar *to = strstr((char *)buddy_name, "sip:") ? g_strdup((char *)buddy_name) : g_strdup_printf("sip:%s", (char *)buddy_name);
1664 gchar *tmp = get_contact(sip);
1665 gchar *request;
1666 gchar *content;
1667 gchar *autoextend = "";
1669 if (!sip->msrtc_event_categories)
1670 autoextend = "Supported: com.microsoft.autoextend\r\n";
1672 request = g_strdup_printf(
1673 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1674 "Supported: ms-piggyback-first-notify\r\n"
1675 "%sSupported: ms-benotify\r\n"
1676 "Proxy-Require: ms-benotify\r\n"
1677 "Event: presence\r\n"
1678 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1679 "Contact: %s\r\n", autoextend,tmp);
1681 content = g_strdup_printf(
1682 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1683 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1684 "<resource uri=\"%s\"/>\n"
1685 "</adhocList>\n"
1686 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1687 "<category name=\"note\"/>\n"
1688 "<category name=\"state\"/>\n"
1689 "</categoryList>\n"
1690 "</action>\n"
1691 "</batchSub>", sip->username, to
1694 g_free(tmp);
1696 /* subscribe to buddy presence */
1697 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1699 g_free(content);
1700 g_free(to);
1701 g_free(request);
1704 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1706 if (!purple_status_is_active(status))
1707 return;
1709 if (account->gc) {
1710 struct sipe_account_data *sip = account->gc->proto_data;
1712 if (sip) {
1713 g_free(sip->status);
1714 sip->status = g_strdup(purple_status_get_id(status));
1715 send_presence_status(sip);
1720 static void
1721 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1723 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1724 sipe_group_set_user(sip, name);
1727 static void
1728 sipe_group_buddy(PurpleConnection *gc,
1729 const char *who,
1730 const char *old_group_name,
1731 const char *new_group_name)
1733 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1734 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1735 struct sipe_group * old_group = NULL;
1736 struct sipe_group * new_group;
1738 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1739 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1741 if(!buddy) { // buddy not in roaming list
1742 return;
1745 if (old_group_name) {
1746 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1748 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1750 if (old_group) {
1751 buddy->groups = g_slist_remove(buddy->groups, old_group);
1752 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1755 if (!new_group) {
1756 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1757 } else {
1758 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1759 sipe_group_set_user(sip, who);
1763 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1765 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1766 struct sipe_buddy *b;
1768 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1770 // Prepend sip: if needed
1771 if (strncmp("sip:", buddy->name, 4)) {
1772 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1773 purple_blist_rename_buddy(buddy, buf);
1774 g_free(buf);
1777 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1778 b = g_new0(struct sipe_buddy, 1);
1779 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1780 b->name = g_strdup(buddy->name);
1781 g_hash_table_insert(sip->buddies, b->name, b);
1782 sipe_group_buddy(gc, b->name, NULL, group->name);
1783 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1784 } else {
1785 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1789 static void sipe_free_buddy(struct sipe_buddy *buddy)
1791 g_free(buddy->name);
1792 g_free(buddy->annotation);
1793 g_free(buddy->device_name);
1794 g_slist_free(buddy->groups);
1795 g_free(buddy);
1799 * Unassociates buddy from group first.
1800 * Then see if no groups left, removes buddy completely.
1801 * Otherwise updates buddy groups on server.
1803 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1805 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1806 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1807 struct sipe_group *g = NULL;
1809 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1811 if (!b) return;
1813 if (group) {
1814 g = sipe_group_find_by_name(sip, group->name);
1817 if (g) {
1818 b->groups = g_slist_remove(b->groups, g);
1819 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1822 if (g_slist_length(b->groups) < 1) {
1823 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1824 sipe_cancel_scheduled_action(sip, action_name);
1825 g_free(action_name);
1827 g_hash_table_remove(sip->buddies, buddy->name);
1829 if (b->name) {
1830 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1831 send_soap_request(sip, body);
1832 g_free(body);
1835 sipe_free_buddy(b);
1836 } else {
1837 //updates groups on server
1838 sipe_group_set_user(sip, b->name);
1843 static void
1844 sipe_rename_group(PurpleConnection *gc,
1845 const char *old_name,
1846 PurpleGroup *group,
1847 GList *moved_buddies)
1849 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1850 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1851 if (group) {
1852 sipe_group_rename(sip, s_group, group->name);
1853 } else {
1854 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1858 static void
1859 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1861 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1862 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1863 if (s_group) {
1864 gchar *body;
1865 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1866 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1867 send_soap_request(sip, body);
1868 g_free(body);
1870 sip->groups = g_slist_remove(sip->groups, s_group);
1871 g_free(s_group->name);
1872 g_free(s_group);
1873 } else {
1874 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1878 static GList *sipe_status_types(PurpleAccount *acc)
1880 PurpleStatusType *type;
1881 GList *types = NULL;
1883 // Online
1884 type = purple_status_type_new_with_attrs(
1885 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1886 // Translators: noun
1887 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1888 NULL);
1889 types = g_list_append(types, type);
1891 // Busy
1892 type = purple_status_type_new_with_attrs(
1893 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1894 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1895 NULL);
1896 types = g_list_append(types, type);
1898 // Do Not Disturb (not user settable)
1899 type = purple_status_type_new_with_attrs(
1900 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1901 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1902 NULL);
1903 types = g_list_append(types, type);
1905 // Be Right Back
1906 type = purple_status_type_new_with_attrs(
1907 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1908 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1909 NULL);
1910 types = g_list_append(types, type);
1912 // Away
1913 type = purple_status_type_new_with_attrs(
1914 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1915 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1916 NULL);
1917 types = g_list_append(types, type);
1919 //On The Phone
1920 type = purple_status_type_new_with_attrs(
1921 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1922 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1923 NULL);
1924 types = g_list_append(types, type);
1926 //Out To Lunch
1927 type = purple_status_type_new_with_attrs(
1928 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1929 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1930 NULL);
1931 types = g_list_append(types, type);
1933 //Appear Offline
1934 type = purple_status_type_new_full(
1935 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1936 types = g_list_append(types, type);
1938 // Offline
1939 type = purple_status_type_new_full(
1940 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1941 types = g_list_append(types, type);
1943 return types;
1947 * A callback for g_hash_table_foreach
1949 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1951 sipe_subscribe_presence_single(sip, buddy->name);
1955 * Removes entries from purple buddy list
1956 * that does not correspond ones in the roaming contact list.
1958 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1959 GSList *buddies = purple_find_buddies(sip->account, NULL);
1960 GSList *entry = buddies;
1961 struct sipe_buddy *buddy;
1962 PurpleBuddy *b;
1963 PurpleGroup *g;
1965 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1966 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1967 while (entry) {
1968 b = entry->data;
1969 g = purple_buddy_get_group(b);
1970 buddy = g_hash_table_lookup(sip->buddies, b->name);
1971 if(buddy) {
1972 gboolean in_sipe_groups = FALSE;
1973 GSList *entry2 = buddy->groups;
1974 while (entry2) {
1975 struct sipe_group *group = entry2->data;
1976 if (!strcmp(group->name, g->name)) {
1977 in_sipe_groups = TRUE;
1978 break;
1980 entry2 = entry2->next;
1982 if(!in_sipe_groups) {
1983 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1984 purple_blist_remove_buddy(b);
1986 } else {
1987 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1988 purple_blist_remove_buddy(b);
1990 entry = entry->next;
1994 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1996 int len = msg->bodylen;
1998 gchar *tmp = sipmsg_find_header(msg, "Event");
1999 xmlnode *item;
2000 xmlnode *isc;
2001 const gchar *contacts_delta;
2002 xmlnode *group_node;
2003 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
2004 return FALSE;
2007 /* Convert the contact from XML to Purple Buddies */
2008 isc = xmlnode_from_str(msg->body, len);
2009 if (!isc) {
2010 return FALSE;
2013 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
2014 if (contacts_delta) {
2015 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2018 /* Parse groups */
2019 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
2020 struct sipe_group * group = g_new0(struct sipe_group, 1);
2021 const char *name = xmlnode_get_attrib(group_node, "name");
2023 if (!strncmp(name, "~", 1)) {
2024 // TODO translate
2025 name = "Other Contacts";
2027 group->name = g_strdup(name);
2028 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2030 sipe_group_add(sip, group);
2033 // Make sure we have at least one group
2034 if (g_slist_length(sip->groups) == 0) {
2035 struct sipe_group * group = g_new0(struct sipe_group, 1);
2036 PurpleGroup *purple_group;
2037 // TODO translate
2038 group->name = g_strdup("Other Contacts");
2039 group->id = 1;
2040 purple_group = purple_group_new(group->name);
2041 purple_blist_add_group(purple_group, NULL);
2042 sip->groups = g_slist_append(sip->groups, group);
2045 /* Parse contacts */
2046 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2047 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
2048 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
2049 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
2050 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
2051 gchar **item_groups;
2052 struct sipe_group *group = NULL;
2053 struct sipe_buddy *buddy = NULL;
2054 int i = 0;
2056 // assign to group Other Contacts if nothing else received
2057 if(!groups || !strcmp("", groups) ) {
2058 group = sipe_group_find_by_name(sip, "Other Contacts");
2059 groups = group ? g_strdup_printf("%d", group->id) : "1";
2062 item_groups = g_strsplit(groups, " ", 0);
2064 while (item_groups[i]) {
2065 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2067 // If couldn't find the right group for this contact, just put them in the first group we have
2068 if (group == NULL && g_slist_length(sip->groups) > 0) {
2069 group = sip->groups->data;
2072 if (group != NULL) {
2073 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2074 if (!b){
2075 b = purple_buddy_new(sip->account, buddy_name, uri);
2076 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2079 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2080 if (name != NULL && strlen(name) != 0) {
2081 purple_blist_alias_buddy(b, name);
2085 if (!buddy) {
2086 buddy = g_new0(struct sipe_buddy, 1);
2087 buddy->name = g_strdup(b->name);
2088 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2091 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2093 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2094 } else {
2095 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2096 name);
2099 i++;
2100 } // while, contact groups
2101 g_strfreev(item_groups);
2102 g_free(groups);
2103 g_free(name);
2104 g_free(buddy_name);
2105 g_free(uri);
2107 } // for, contacts
2109 xmlnode_free(isc);
2111 sipe_cleanup_local_blist(sip);
2113 //subscribe to buddies
2114 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2115 if(sip->batched_support){
2116 sipe_subscribe_presence_batched(sip, NULL);
2118 else{
2119 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2121 sip->subscribed_buddies = TRUE;
2124 return 0;
2128 * Subscribe roaming contacts
2130 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2132 gchar *to = g_strdup_printf("sip:%s", sip->username);
2133 gchar *tmp = get_contact(sip);
2134 gchar *hdr = g_strdup_printf(
2135 "Event: vnd-microsoft-roaming-contacts\r\n"
2136 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2137 "Supported: com.microsoft.autoextend\r\n"
2138 "Supported: ms-benotify\r\n"
2139 "Proxy-Require: ms-benotify\r\n"
2140 "Supported: ms-piggyback-first-notify\r\n"
2141 "Contact: %s\r\n", tmp);
2142 g_free(tmp);
2144 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2145 g_free(to);
2146 g_free(hdr);
2149 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, void *unused)
2151 gchar *to = g_strdup_printf("sip:%s", sip->username);
2152 gchar *tmp = get_contact(sip);
2153 gchar *hdr = g_strdup_printf(
2154 "Event: presence.wpending\r\n"
2155 "Accept: text/xml+msrtc.wpending\r\n"
2156 "Supported: com.microsoft.autoextend\r\n"
2157 "Supported: ms-benotify\r\n"
2158 "Proxy-Require: ms-benotify\r\n"
2159 "Supported: ms-piggyback-first-notify\r\n"
2160 "Contact: %s\r\n", tmp);
2161 g_free(tmp);
2163 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2164 g_free(to);
2165 g_free(hdr);
2169 * Fires on deregistration event initiated by server.
2170 * [MS-SIPREGE] SIP extension.
2173 // 2007 Example
2175 // Content-Type: text/registration-event
2176 // subscription-state: terminated;expires=0
2177 // ms-diagnostics-public: 4141;reason="User disabled"
2179 // deregistered;event=rejected
2181 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2183 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2184 gchar *event = NULL;
2185 gchar *reason = NULL;
2186 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2188 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2189 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2191 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2192 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2193 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2194 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2195 } else {
2196 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2197 return;
2200 if (warning != NULL) {
2201 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2202 } else { // for LCS2005
2203 int error_id = 0;
2204 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2205 error_id = 4140; // [MS-SIPREGE]
2206 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2207 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2208 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2209 error_id = 4141;
2210 reason = g_strdup(_("User disabled")); // [MS-OCER]
2211 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2212 error_id = 4142;
2213 reason = g_strdup(_("User moved")); // [MS-OCER]
2216 g_free(event);
2217 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2218 g_free(reason);
2220 sip->gc->wants_to_die = TRUE;
2221 purple_connection_error(sip->gc, warning);
2222 g_free(warning);
2226 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2228 const gchar *contacts_delta;
2229 xmlnode *xml;
2231 xml = xmlnode_from_str(msg->body, msg->bodylen);
2232 if (!xml)
2234 return;
2237 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2238 if (contacts_delta)
2240 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2243 xmlnode_free(xml);
2249 * When we receive some self (BE) NOTIFY with a new subscriber
2250 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2254 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2256 gchar *contact;
2257 gchar *to;
2258 xmlnode *xml;
2259 xmlnode *node;
2260 char *display_name = NULL;
2261 PurpleBuddy *pbuddy;
2262 const char *alias;
2263 char *uri_alias;
2264 char *uri_user;
2266 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2268 xml = xmlnode_from_str(msg->body, msg->bodylen);
2269 if (!xml) return;
2271 contact = get_contact(sip);
2272 to = g_strdup_printf("sip:%s", sip->username);
2274 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2275 const char *user;
2276 const char *acknowledged;
2277 gchar *hdr;
2278 gchar *body;
2280 user = xmlnode_get_attrib(node, "user");
2281 if (!user) continue;
2282 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2283 uri_user = g_strdup_printf("sip:%s", user);
2284 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2285 if(pbuddy){
2286 alias = purple_buddy_get_local_alias(pbuddy);
2287 uri_alias = g_strdup_printf("sip:%s", alias);
2288 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2289 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2290 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2291 purple_blist_alias_buddy(pbuddy, display_name);
2293 g_free(uri_alias);
2295 g_free(uri_user);
2297 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2298 if(!g_ascii_strcasecmp(acknowledged,"false")){
2299 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2300 hdr = g_strdup_printf(
2301 "Contact: %s\r\n"
2302 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2304 body = g_strdup_printf(
2305 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2306 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2307 "</setSubscribers>", user);
2309 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2310 g_free(body);
2311 g_free(hdr);
2315 g_free(to);
2316 g_free(contact);
2317 xmlnode_free(xml);
2320 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2322 gchar *to = g_strdup_printf("sip:%s", sip->username);
2323 gchar *tmp = get_contact(sip);
2324 gchar *hdr = g_strdup_printf(
2325 "Event: vnd-microsoft-roaming-ACL\r\n"
2326 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2327 "Supported: com.microsoft.autoextend\r\n"
2328 "Supported: ms-benotify\r\n"
2329 "Proxy-Require: ms-benotify\r\n"
2330 "Supported: ms-piggyback-first-notify\r\n"
2331 "Contact: %s\r\n", tmp);
2332 g_free(tmp);
2334 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2335 g_free(to);
2336 g_free(hdr);
2340 * To request for presence information about the user, access level settings that have already been configured by the user
2341 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2342 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2345 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2347 gchar *to = g_strdup_printf("sip:%s", sip->username);
2348 gchar *tmp = get_contact(sip);
2349 gchar *hdr = g_strdup_printf(
2350 "Event: vnd-microsoft-roaming-self\r\n"
2351 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2352 "Supported: ms-benotify\r\n"
2353 "Proxy-Require: ms-benotify\r\n"
2354 "Supported: ms-piggyback-first-notify\r\n"
2355 "Contact: %s\r\n"
2356 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2358 gchar *body=g_strdup(
2359 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2360 "<roaming type=\"categories\"/>"
2361 "<roaming type=\"containers\"/>"
2362 "<roaming type=\"subscribers\"/></roamingList>");
2364 g_free(tmp);
2365 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2366 g_free(body);
2367 g_free(to);
2368 g_free(hdr);
2372 * For 2005 version
2374 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2376 gchar *to = g_strdup_printf("sip:%s", sip->username);
2377 gchar *tmp = get_contact(sip);
2378 gchar *hdr = g_strdup_printf(
2379 "Event: vnd-microsoft-provisioning\r\n"
2380 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2381 "Supported: com.microsoft.autoextend\r\n"
2382 "Supported: ms-benotify\r\n"
2383 "Proxy-Require: ms-benotify\r\n"
2384 "Supported: ms-piggyback-first-notify\r\n"
2385 "Expires: 0\r\n"
2386 "Contact: %s\r\n", tmp);
2388 g_free(tmp);
2389 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2390 g_free(to);
2391 g_free(hdr);
2394 /** Subscription for provisioning information to help with initial
2395 * configuration. This subscription is a one-time query (denoted by the Expires header,
2396 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2397 * configuration, meeting policies, and policy settings that Communicator must enforce.
2398 * TODO: for what we need this information.
2401 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2403 gchar *to = g_strdup_printf("sip:%s", sip->username);
2404 gchar *tmp = get_contact(sip);
2405 gchar *hdr = g_strdup_printf(
2406 "Event: vnd-microsoft-provisioning-v2\r\n"
2407 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2408 "Supported: com.microsoft.autoextend\r\n"
2409 "Supported: ms-benotify\r\n"
2410 "Proxy-Require: ms-benotify\r\n"
2411 "Supported: ms-piggyback-first-notify\r\n"
2412 "Expires: 0\r\n"
2413 "Contact: %s\r\n"
2414 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2415 gchar *body = g_strdup(
2416 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2417 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2418 "<provisioningGroup name=\"ucPolicy\"/>"
2419 "</provisioningGroupList>");
2421 g_free(tmp);
2422 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2423 g_free(body);
2424 g_free(to);
2425 g_free(hdr);
2428 /* IM Session (INVITE and MESSAGE methods) */
2430 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2432 struct sip_im_session *session;
2433 GSList *entry;
2434 if (sip == NULL || who == NULL) {
2435 return NULL;
2438 entry = sip->im_sessions;
2439 while (entry) {
2440 session = entry->data;
2441 if ((who != NULL && !strcmp(who, session->with))) {
2442 return session;
2444 entry = entry->next;
2446 return NULL;
2449 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2451 struct sip_im_session *session = find_im_session(sip, who);
2452 if (!session) {
2453 session = g_new0(struct sip_im_session, 1);
2454 session->with = g_strdup(who);
2455 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2456 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2458 return session;
2461 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2463 struct sip_dialog *dialog = session->dialog;
2464 GSList *entry;
2466 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2468 if (dialog) {
2469 entry = dialog->routes;
2470 while (entry) {
2471 g_free(entry->data);
2472 entry = g_slist_remove(entry, entry->data);
2474 entry = dialog->supported;
2475 while (entry) {
2476 g_free(entry->data);
2477 entry = g_slist_remove(entry, entry->data);
2479 g_free(dialog->callid);
2480 g_free(dialog->ourtag);
2481 g_free(dialog->theirtag);
2482 g_free(dialog->theirepid);
2483 g_free(dialog->request);
2485 g_free(session->dialog);
2487 entry = session->outgoing_message_queue;
2488 while (entry) {
2489 g_free(entry->data);
2490 entry = g_slist_remove(entry, entry->data);
2493 g_hash_table_destroy(session->unconfirmed_messages);
2495 g_free(session->with);
2496 g_free(session);
2499 static gboolean
2500 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2502 gboolean ret = TRUE;
2504 if (msg->response != 200) {
2505 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2506 return FALSE;
2509 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2511 return ret;
2515 * Asks UA/proxy about its capabilities.
2517 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2519 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2520 gchar *contact = get_contact(sip);
2521 gchar *request;
2522 request = g_strdup_printf(
2523 "Accept: application/sdp\r\n"
2524 "Contact: %s\r\n", contact);
2526 g_free(contact);
2528 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2530 g_free(to);
2531 g_free(request);
2534 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2536 char *msg, *msg_tmp;
2537 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2538 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2539 g_free(msg_tmp);
2540 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2541 "possibly because one or more persons are offline:\n%s") ,
2542 msg ? msg : "");
2543 purple_conv_present_error(with, sip->account, msg_tmp);
2544 g_free(msg);
2545 g_free(msg_tmp);
2548 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2550 static gboolean
2551 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2553 gboolean ret = TRUE;
2554 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2555 struct sip_im_session * session = find_im_session(sip, with);
2556 struct sip_dialog *dialog;
2557 gchar *cseq;
2558 char *key;
2559 gchar *message;
2561 if (!session) {
2562 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2563 g_free(with);
2564 return FALSE;
2567 dialog = session->dialog;
2568 if (!dialog) {
2569 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2570 g_free(with);
2571 return FALSE;
2574 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2575 key = g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq));
2576 g_free(cseq);
2577 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2579 if (msg->response != 200) {
2580 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2582 sipe_present_message_undelivered_err(with, sip, message);
2583 im_session_destroy(sip, session);
2584 ret = FALSE;
2585 } else {
2586 g_hash_table_remove(session->unconfirmed_messages, key);
2587 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2588 key, g_hash_table_size(session->unconfirmed_messages));
2591 g_free(key);
2592 g_free(with);
2594 sipe_im_process_queue(sip, session);
2595 return ret;
2598 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2600 gchar *hdr;
2601 gchar *fullto;
2602 gchar *tmp;
2603 char *msgformat;
2604 char *msgtext;
2605 gchar *msgr_value;
2606 gchar *msgr;
2608 if (strncmp("sip:", session->with, 4)) {
2609 fullto = g_strdup_printf("sip:%s", session->with);
2610 } else {
2611 fullto = g_strdup(session->with);
2614 sipe_parse_html(msg, &msgformat, &msgtext);
2615 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2617 msgr_value = sipmsg_get_msgr_string(msgformat);
2618 g_free(msgformat);
2619 if (msgr_value) {
2620 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2621 g_free(msgr_value);
2622 } else {
2623 msgr = g_strdup("");
2626 tmp = get_contact(sip);
2627 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2628 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2629 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2630 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2631 tmp, msgr);
2632 g_free(tmp);
2633 g_free(msgr);
2635 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2636 g_free(msgtext);
2637 g_free(hdr);
2638 g_free(fullto);
2642 static void
2643 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2645 GSList *entry = session->outgoing_message_queue;
2647 if (session->outgoing_invite) return; //do not send messages until INVITE responded.
2649 while (entry) {
2650 char *key = g_strdup_printf("<%s><%d><MESSAGE>", session->dialog->callid, (session->dialog->cseq) + 1);
2651 char *queued_msg = entry->data;
2652 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2653 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2654 key, g_hash_table_size(session->unconfirmed_messages));
2655 g_free(key);
2656 sipe_send_message(sip, session, queued_msg);
2657 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2658 g_free(queued_msg);
2662 static void
2663 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2665 GSList *hdr = msg->headers;
2666 struct siphdrelement *elem;
2667 gchar *contact;
2669 while(hdr)
2671 elem = hdr->data;
2672 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2674 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2675 dialog->routes = g_slist_append(dialog->routes, route);
2677 hdr = g_slist_next(hdr);
2680 if (outgoing)
2682 dialog->routes = g_slist_reverse(dialog->routes);
2685 if (dialog->routes)
2687 dialog->request = dialog->routes->data;
2688 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2691 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2692 dialog->routes = g_slist_append(dialog->routes, contact);
2695 static void
2696 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2698 GSList *hdr = msg->headers;
2699 struct siphdrelement *elem;
2700 while(hdr)
2702 elem = hdr->data;
2703 if(!g_ascii_strcasecmp(elem->name, "Supported")
2704 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2706 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2709 hdr = g_slist_next(hdr);
2713 static void
2714 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2716 gchar *us = outgoing ? "From" : "To";
2717 gchar *them = outgoing ? "To" : "From";
2719 g_free(dialog->callid);
2720 g_free(dialog->ourtag);
2721 g_free(dialog->theirtag);
2723 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2724 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2725 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2726 if (!dialog->theirepid) {
2727 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2728 if (!dialog->theirepid) {
2729 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2733 // Catch a tag on the end of the To Header and get rid of it.
2734 if (strstr(dialog->theirepid, "tag=")) {
2735 dialog->theirepid = strtok(dialog->theirepid, ";");
2738 sipe_get_route_header(msg, dialog, outgoing);
2739 sipe_get_supported_header(msg, dialog, outgoing);
2743 static gboolean
2744 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2746 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2747 struct sip_im_session * session = find_im_session(sip, with);
2748 struct sip_dialog *dialog;
2749 char *cseq;
2750 char *key;
2751 gchar *message;
2753 if (!session) {
2754 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2755 g_free(with);
2756 return FALSE;
2759 dialog = session->dialog;
2760 if (!dialog) {
2761 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2762 g_free(with);
2763 return FALSE;
2766 sipe_parse_dialog(msg, dialog, TRUE);
2768 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2769 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2770 g_free(cseq);
2771 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2773 if (msg->response != 200) {
2774 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2776 sipe_present_message_undelivered_err(with, sip, message);
2777 im_session_destroy(sip, session);
2778 g_free(with);
2779 return FALSE;
2782 dialog->cseq = 0;
2783 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2784 session->outgoing_invite = NULL;
2785 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2786 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2787 if (session->outgoing_message_queue) {
2788 char *queued_msg = session->outgoing_message_queue->data;
2789 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2790 g_free(queued_msg);
2794 sipe_im_process_queue(sip, session);
2796 g_hash_table_remove(session->unconfirmed_messages, key);
2797 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2798 key, g_hash_table_size(session->unconfirmed_messages));
2800 g_free(key);
2801 g_free(with);
2802 return TRUE;
2806 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session *session, const gchar *msg_body)
2808 gchar *hdr;
2809 gchar *to;
2810 //gchar *from;
2811 gchar *contact;
2812 gchar *body;
2813 char *msgformat;
2814 char *msgtext;
2815 char *base64_msg;
2816 char *ms_text_format;
2817 gchar *msgr_value;
2818 gchar *msgr;
2819 char *key;
2821 if (session->dialog) {
2822 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2823 return;
2826 session->dialog = g_new0(struct sip_dialog, 1);
2827 session->dialog->callid = gencallid();
2829 if (!(session->dialog->ourtag)) {
2830 session->dialog->ourtag = gentag();
2834 if (strstr(session->with, "sip:")) {
2835 to = g_strdup(session->with);
2836 } else {
2837 to = g_strdup_printf("sip:%s", session->with);
2840 sipe_parse_html(msg_body, &msgformat, &msgtext);
2841 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2843 msgr_value = sipmsg_get_msgr_string(msgformat);
2844 g_free(msgformat);
2845 msgr = "";
2846 if (msgr_value) {
2847 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2848 g_free(msgr_value);
2851 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2852 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2853 g_free(msgtext);
2854 g_free(msgr);
2855 g_free(base64_msg);
2857 key = g_strdup_printf("<%s><%d><INVITE>", session->dialog->callid, (session->dialog->cseq) + 1);
2858 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2859 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2860 key, g_hash_table_size(session->unconfirmed_messages));
2861 g_free(key);
2863 contact = get_contact(sip);
2864 /* from = g_strdup_printf("sip:%s", sip->username);*/
2865 hdr = g_strdup_printf(
2866 /*"Supported: ms-delayed-accept\r\n"*/
2867 /*"Roster-Manager: <%s>\r\n"*/
2868 /*"EndPoints: <%s>, <%s>\r\n"*/
2869 /*"Supported: com.microsoft.rtc-multiparty\r\n"*/
2870 "Contact: %s\r\n%s"
2871 "Content-Type: application/sdp\r\n",
2872 contact, ms_text_format);
2873 g_free(ms_text_format);
2875 body = g_strdup_printf(
2876 "v=0\r\n"
2877 "o=- 0 0 IN IP4 %s\r\n"
2878 "s=session\r\n"
2879 "c=IN IP4 %s\r\n"
2880 "t=0 0\r\n"
2881 "m=message %d sip null\r\n"
2882 "a=accept-types:text/plain text/html image/gif "
2883 "multipart/alternative application/im-iscomposing+xml\r\n",
2884 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2886 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2887 to, to, hdr, body, session->dialog, process_invite_response);
2889 g_free(to);
2890 /* g_free(from);*/
2891 g_free(body);
2892 g_free(hdr);
2893 g_free(contact);
2896 static void
2897 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2899 if (session) {
2900 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2901 im_session_destroy(sip, session);
2905 static void
2906 sipe_convo_closed(PurpleConnection * gc, const char *who)
2908 struct sipe_account_data *sip = gc->proto_data;
2910 purple_debug_info("sipe", "conversation with %s closed\n", who);
2911 im_session_close(sip, find_im_session(sip, who));
2914 static void
2915 im_session_close_all (struct sipe_account_data *sip)
2917 GSList *entry = sip->im_sessions;
2918 while (entry) {
2919 im_session_close (sip, entry->data);
2920 entry = sip->im_sessions;
2924 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2926 struct sipe_account_data *sip;
2927 gchar *to;
2928 struct sip_im_session *session;
2930 sip = gc->proto_data;
2931 to = g_strdup(who);
2933 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2935 session = find_or_create_im_session(sip, who);
2937 // Queue the message
2938 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
2940 if (session->dialog && session->dialog->callid) {
2941 sipe_im_process_queue(sip, session);
2942 } else if (!session->outgoing_invite) {
2943 // Need to send the INVITE to get the outgoing dialog setup
2944 sipe_invite(sip, session, what);
2947 g_free(to);
2948 return 1;
2951 /* End IM Session (INVITE and MESSAGE methods) */
2953 static unsigned int
2954 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2956 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2957 struct sip_im_session *session;
2959 if (state == PURPLE_NOT_TYPING)
2960 return 0;
2962 session = find_im_session(sip, who);
2964 if (session && session->dialog) {
2965 send_sip_request(gc, "INFO", who, who,
2966 "Content-Type: application/xml\r\n",
2967 SIPE_SEND_TYPING, session->dialog, NULL);
2969 return SIPE_TYPING_SEND_TIMEOUT;
2972 static gboolean resend_timeout(struct sipe_account_data *sip)
2974 GSList *tmp = sip->transactions;
2975 time_t currtime = time(NULL);
2976 while (tmp) {
2977 struct transaction *trans = tmp->data;
2978 tmp = tmp->next;
2979 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2980 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2981 /* TODO 408 */
2982 } else {
2983 if ((currtime - trans->time > 2) && trans->retries == 0) {
2984 trans->retries++;
2985 sendout_sipmsg(sip, trans->msg);
2989 return TRUE;
2992 static void do_reauthenticate_cb(struct sipe_account_data *sip, void *unused)
2994 /* register again when security token expires */
2995 /* we have to start a new authentication as the security token
2996 * is almost expired by sending a not signed REGISTER message */
2997 purple_debug_info("sipe", "do a full reauthentication\n");
2998 sipe_auth_free(&sip->registrar);
2999 sip->registerstatus = 0;
3000 do_register(sip);
3001 sip->reauthenticate_set = FALSE;
3004 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3006 gchar *from;
3007 gchar *contenttype;
3008 gboolean found = FALSE;
3010 from = parse_from(sipmsg_find_header(msg, "From"));
3012 if (!from) return;
3014 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3016 contenttype = sipmsg_find_header(msg, "Content-Type");
3017 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
3019 gchar *html = get_html_message(contenttype, msg->body);
3020 serv_got_im(sip->gc, from, html, 0, time(NULL));
3021 g_free(html);
3022 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3023 found = TRUE;
3025 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3026 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3027 xmlnode *state;
3028 gchar *statedata;
3030 if (!isc) {
3031 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3032 return;
3035 state = xmlnode_get_child(isc, "state");
3037 if (!state) {
3038 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3039 xmlnode_free(isc);
3040 return;
3043 statedata = xmlnode_get_data(state);
3044 if (statedata) {
3045 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3046 else serv_got_typing_stopped(sip->gc, from);
3048 g_free(statedata);
3050 xmlnode_free(isc);
3051 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3052 found = TRUE;
3054 if (!found) {
3055 purple_debug_info("sipe", "got unknown mime-type");
3056 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3058 g_free(from);
3061 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3063 gchar *ms_text_format;
3064 gchar *from;
3065 gchar *body;
3066 gchar *newTag = gentag();
3067 struct sip_im_session *session;
3069 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3071 // Only accept text invitations
3072 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3073 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3074 return;
3077 // TODO There *must* be a better way to clean up the To header to add a tag...
3078 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3079 gchar *oldHeader;
3080 gchar *newHeader;
3081 oldHeader = sipmsg_find_header(msg, "To");
3082 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3083 sipmsg_remove_header(msg, "To");
3084 sipmsg_add_header(msg, "To", newHeader);
3085 g_free(newHeader);
3087 from = parse_from(sipmsg_find_header(msg, "From"));
3088 session = find_or_create_im_session (sip, from);
3089 if (session) {
3090 if (session->dialog) {
3091 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3092 } else {
3093 session->dialog = g_new0(struct sip_dialog, 1);
3095 sipe_parse_dialog(msg, session->dialog, FALSE);
3097 // This stuff is the same stuff that happens in the sipe_parse_dialog...
3098 //session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3099 //session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3100 //session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3101 //session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3103 if (!session->dialog->ourtag) {
3104 session->dialog->ourtag = newTag;
3105 newTag = NULL;
3108 } else {
3109 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3111 g_free(newTag);
3113 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3114 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3115 if (ms_text_format) {
3116 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3118 gchar *html = get_html_message(ms_text_format, NULL);
3119 if (html) {
3120 serv_got_im(sip->gc, from, html, 0, time(NULL));
3121 g_free(html);
3122 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3126 g_free(from);
3128 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3129 sipmsg_remove_header(msg, "Ms-Text-Format");
3130 sipmsg_remove_header(msg, "EndPoints");
3131 sipmsg_remove_header(msg, "User-Agent");
3132 sipmsg_remove_header(msg, "Roster-Manager");
3133 sipmsg_remove_header(msg, "P-Asserted-Identity");
3135 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3136 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3138 body = g_strdup_printf(
3139 "v=0\r\n"
3140 "o=- 0 0 IN IP4 %s\r\n"
3141 "s=session\r\n"
3142 "c=IN IP4 %s\r\n"
3143 "t=0 0\r\n"
3144 "m=message %d sip sip:%s\r\n"
3145 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3146 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3147 sip->realport, sip->username);
3148 send_sip_response(sip->gc, msg, 200, "OK", body);
3149 g_free(body);
3152 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3154 gchar *body;
3156 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3157 sipmsg_remove_header(msg, "EndPoints");
3158 sipmsg_remove_header(msg, "User-Agent");
3160 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3161 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3163 body = g_strdup_printf(
3164 "v=0\r\n"
3165 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3166 "s=session\r\n"
3167 "c=IN IP4 0.0.0.0\r\n"
3168 "t=0 0\r\n"
3169 "m=message %d sip sip:%s\r\n"
3170 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3171 sip->realport, sip->username);
3172 send_sip_response(sip->gc, msg, 200, "OK", body);
3173 g_free(body);
3176 static void sipe_connection_cleanup(struct sipe_account_data *);
3177 static void create_connection(struct sipe_account_data *, gchar *, int);
3179 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3181 gchar *tmp;
3182 const gchar *expires_header;
3183 int expires, i;
3184 GSList *hdr = msg->headers;
3185 GSList *entry;
3186 struct siphdrelement *elem;
3188 expires_header = sipmsg_find_header(msg, "Expires");
3189 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3190 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3192 switch (msg->response) {
3193 case 200:
3194 if (expires == 0) {
3195 sip->registerstatus = 0;
3196 } else {
3197 gchar *contact_hdr = NULL;
3198 gchar *gruu = NULL;
3199 gchar *epid;
3200 gchar *uuid;
3201 gchar *timeout;
3203 if (!sip->reregister_set) {
3204 gchar *action_name = g_strdup_printf("<%s>", "registration");
3205 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3206 g_free(action_name);
3207 sip->reregister_set = TRUE;
3210 sip->registerstatus = 3;
3212 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3213 tmp = sipmsg_find_auth_header(msg, "NTLM");
3214 } else {
3215 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3217 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3218 fill_auth(sip, tmp, &sip->registrar);
3220 if (!sip->reauthenticate_set) {
3221 /* we have to reauthenticate as our security token expires
3222 after eight hours (be five minutes early) */
3223 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3224 guint reauth_timeout = (8 * 3600) - 360;
3225 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3226 g_free(action_name);
3227 sip->reauthenticate_set = TRUE;
3230 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3232 epid = get_epid(sip);
3233 uuid = generateUUIDfromEPID(epid);
3234 g_free(epid);
3236 // There can be multiple Contact headers (one per location where the user is logged in) so
3237 // make sure to only get the one for this uuid
3238 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3239 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3240 if (valid_contact) {
3241 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3242 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3243 g_free(valid_contact);
3244 break;
3245 } else {
3246 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3249 g_free(uuid);
3251 g_free(sip->contact);
3252 if(gruu) {
3253 sip->contact = g_strdup_printf("<%s>", gruu);
3254 g_free(gruu);
3255 } else {
3256 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3257 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);
3259 sip->msrtc_event_categories = FALSE;
3260 sip->batched_support = FALSE;
3262 while(hdr)
3264 elem = hdr->data;
3265 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3266 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3267 sip->msrtc_event_categories = TRUE;
3268 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3270 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3271 sip->batched_support = TRUE;
3272 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3275 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3276 gchar **caps = g_strsplit(elem->value,",",0);
3277 i = 0;
3278 while (caps[i]) {
3279 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3280 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3281 i++;
3283 g_strfreev(caps);
3285 hdr = g_slist_next(hdr);
3288 if (!sip->subscribed) { //do it just once, not every re-register
3289 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3290 //sipe_options_request(sip, sip->sipdomain);
3292 entry = sip->allow_events;
3293 while (entry) {
3294 tmp = entry->data;
3295 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3296 sipe_subscribe_roaming_contacts(sip, msg);
3298 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3299 sipe_subscribe_roaming_acl(sip, msg);
3301 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3302 sipe_subscribe_roaming_self(sip, msg);
3304 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3305 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3306 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3307 sipe_subscribe_roaming_provisioning(sip, msg);
3309 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3310 sipe_subscribe_presence_wpending(sip, msg);
3312 entry = entry->next;
3314 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3315 sip->subscribed = TRUE;
3318 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3319 "timeout=", ";", NULL);
3320 if (timeout != NULL) {
3321 sscanf(timeout, "%u", &sip->keepalive_timeout);
3322 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3323 sip->keepalive_timeout);
3324 g_free(timeout);
3325 } else {
3326 sip->keepalive_timeout = 300;
3329 // Should we remove the transaction here?
3330 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3331 transactions_remove(sip, tc);
3333 break;
3334 case 301:
3336 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3338 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3339 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3340 gchar **tmp;
3341 gchar *hostname;
3342 int port = 0;
3343 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3344 int i = 1;
3346 tmp = g_strsplit(parts[0], ":", 0);
3347 hostname = g_strdup(tmp[0]);
3348 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3349 g_strfreev(tmp);
3351 while (parts[i]) {
3352 tmp = g_strsplit(parts[i], "=", 0);
3353 if (tmp[1]) {
3354 if (g_strcasecmp("transport", tmp[0]) == 0) {
3355 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3356 transport = SIPE_TRANSPORT_TCP;
3357 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3358 transport = SIPE_TRANSPORT_UDP;
3362 g_strfreev(tmp);
3363 i++;
3365 g_strfreev(parts);
3367 /* Close old connection */
3368 sipe_connection_cleanup(sip);
3370 /* Create new connection */
3371 sip->transport = transport;
3372 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3373 hostname, port, TRANSPORT_DESCRIPTOR);
3374 create_connection(sip, hostname, port);
3376 g_free(redirect);
3378 break;
3379 case 401:
3380 if (sip->registerstatus != 2) {
3381 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3382 if (sip->registrar.retries > 3) {
3383 sip->gc->wants_to_die = TRUE;
3384 purple_connection_error(sip->gc, _("Wrong Password"));
3385 return TRUE;
3387 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3388 tmp = sipmsg_find_auth_header(msg, "NTLM");
3389 } else {
3390 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3392 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3393 fill_auth(sip, tmp, &sip->registrar);
3394 sip->registerstatus = 2;
3395 if (sip->account->disconnecting) {
3396 do_register_exp(sip, 0);
3397 } else {
3398 do_register(sip);
3401 break;
3402 case 403:
3404 gchar *warning = sipmsg_find_header(msg, "Warning");
3405 if (warning != NULL) {
3406 /* Example header:
3407 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3409 gchar **tmp = g_strsplit(warning, "\"", 0);
3410 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3411 g_strfreev(tmp);
3412 } else {
3413 warning = g_strdup(_("You have been rejected by the server"));
3416 sip->gc->wants_to_die = TRUE;
3417 purple_connection_error(sip->gc, warning);
3418 g_free(warning);
3419 return TRUE;
3421 break;
3422 case 404:
3424 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3425 if (warning != NULL) {
3426 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3427 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3428 g_free(reason);
3429 } else {
3430 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3433 sip->gc->wants_to_die = TRUE;
3434 purple_connection_error(sip->gc, warning);
3435 g_free(warning);
3436 return TRUE;
3438 break;
3439 case 503:
3441 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3442 if (warning != NULL) {
3443 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3444 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3445 g_free(reason);
3446 } else {
3447 warning = g_strdup(_("Service unavailable: no reason given"));
3450 sip->gc->wants_to_die = TRUE;
3451 purple_connection_error(sip->gc, warning);
3452 g_free(warning);
3453 return TRUE;
3455 break;
3457 return TRUE;
3460 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3462 const char *uri;
3463 xmlnode *xn_categories;
3464 xmlnode *xn_category;
3465 xmlnode *xn_node;
3466 const char *activity = NULL;
3468 xn_categories = xmlnode_from_str(data, len);
3469 uri = xmlnode_get_attrib(xn_categories, "uri");
3471 for (xn_category = xmlnode_get_child(xn_categories, "category");
3472 xn_category ;
3473 xn_category = xmlnode_get_next_twin(xn_category) )
3475 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3477 if (!strcmp(attrVar, "note"))
3479 char *note;
3480 struct sipe_buddy *sbuddy;
3481 xn_node = xmlnode_get_child(xn_category, "note");
3482 if (!xn_node) continue;
3483 xn_node = xmlnode_get_child(xn_node, "body");
3484 if (!xn_node) continue;
3486 note = xmlnode_get_data(xn_node);
3488 if(uri){
3489 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3491 if (sbuddy && note)
3493 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note);
3494 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3495 sbuddy->annotation = g_strdup(note);
3497 if(note)
3498 g_free(note);
3500 else if(!strcmp(attrVar, "state"))
3502 char *data;
3503 int avail;
3504 xn_node = xmlnode_get_child(xn_category, "state");
3505 if (!xn_node) continue;
3506 xn_node = xmlnode_get_child(xn_node, "availability");
3507 if (!xn_node) continue;
3509 data = xmlnode_get_data(xn_node);
3510 avail = atoi(data);
3511 g_free(data);
3513 if (avail < 3000)
3514 activity = SIPE_STATUS_ID_UNKNOWN;
3515 else if (avail < 4500)
3516 activity = SIPE_STATUS_ID_AVAILABLE;
3517 else if (avail < 6000)
3518 activity = SIPE_STATUS_ID_BRB;
3519 else if (avail < 7500)
3520 activity = SIPE_STATUS_ID_ONPHONE;
3521 else if (avail < 9000)
3522 activity = SIPE_STATUS_ID_BUSY;
3523 else if (avail < 12000)
3524 activity = SIPE_STATUS_ID_DND;
3525 else if (avail < 18000)
3526 activity = SIPE_STATUS_ID_AWAY;
3527 else
3528 activity = SIPE_STATUS_ID_OFFLINE;
3531 if(activity) {
3532 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
3533 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3536 xmlnode_free(xn_categories);
3539 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3541 xmlnode *xn_list;
3542 xmlnode *xn_resource;
3543 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
3544 g_free, NULL);
3545 GSList *server;
3546 gchar *host;
3547 GHashTableIter iter;
3549 xn_list = xmlnode_from_str(data, len);
3551 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3552 xn_resource;
3553 xn_resource = xmlnode_get_next_twin(xn_resource) )
3555 const char *uri, *state;
3556 xmlnode *xn_instance;
3558 xn_instance = xmlnode_get_child(xn_resource, "instance");
3559 if (!xn_instance) continue;
3561 uri = xmlnode_get_attrib(xn_resource, "uri");
3562 state = xmlnode_get_attrib(xn_instance, "state");
3563 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
3565 if (strstr(state, "resubscribe")) {
3566 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
3567 struct sipe_buddy *sbuddy;
3568 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
3569 gchar *user = g_strdup(uri);
3570 host = g_strdup(poolFqdn);
3571 server = g_hash_table_lookup(servers, host);
3572 server = g_slist_append(server, user);
3573 g_hash_table_insert(servers, host, server);
3574 } else {
3575 sipe_subscribe_presence_single(sip, (void *) uri);
3577 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3578 if (sbuddy) {
3579 sbuddy->resubscribed = TRUE;
3584 g_hash_table_iter_init(&iter, servers);
3585 while (g_hash_table_iter_next(&iter, (gpointer) &host, (gpointer) &server)) {
3586 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3587 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
3588 payload->host = g_strdup(host);
3589 payload->buddies = server;
3590 sipe_subscribe_presence_batched_routed(sip, payload);
3591 sipe_subscribe_presence_batched_routed_free(payload);
3593 g_hash_table_destroy(servers);
3595 xmlnode_free(xn_list);
3598 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3600 const gchar *uri;
3601 gchar *getbasic;
3602 gchar *activity = NULL;
3603 xmlnode *pidf;
3604 xmlnode *basicstatus = NULL, *tuple, *status;
3605 gboolean isonline = FALSE;
3606 xmlnode *display_name_node;
3608 pidf = xmlnode_from_str(data, len);
3609 if (!pidf) {
3610 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3611 return;
3614 uri = xmlnode_get_attrib(pidf, "entity");
3616 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3618 if ((status = xmlnode_get_child(tuple, "status"))) {
3619 basicstatus = xmlnode_get_child(status, "basic");
3623 if (!basicstatus) {
3624 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3625 xmlnode_free(pidf);
3626 return;
3629 getbasic = xmlnode_get_data(basicstatus);
3630 if (!getbasic) {
3631 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3632 xmlnode_free(pidf);
3633 return;
3636 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3637 if (strstr(getbasic, "open")) {
3638 isonline = TRUE;
3640 g_free(getbasic);
3642 display_name_node = xmlnode_get_child(pidf, "display-name");
3643 // updating display name if alias was just URI
3644 if (display_name_node) {
3645 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3646 GSList *entry = buddies;
3647 PurpleBuddy *p_buddy;
3648 char * display_name = xmlnode_get_data(display_name_node);
3650 while (entry) {
3651 const char *server_alias;
3652 char *alias;
3654 p_buddy = entry->data;
3656 alias = (char *)purple_buddy_get_alias(p_buddy);
3657 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3658 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3659 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3660 purple_blist_alias_buddy(p_buddy, display_name);
3662 g_free(alias);
3664 server_alias = purple_buddy_get_server_alias(p_buddy);
3665 if (display_name &&
3666 ( (server_alias && strcmp(display_name, server_alias))
3667 || !server_alias || strlen(server_alias) == 0 )
3669 purple_blist_server_alias_buddy(p_buddy, display_name);
3672 entry = entry->next;
3674 g_free(display_name);
3677 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3678 if ((status = xmlnode_get_child(tuple, "status"))) {
3679 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3680 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3681 activity = xmlnode_get_data(basicstatus);
3682 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3688 if (isonline) {
3689 const gchar * status_id = NULL;
3690 if (activity) {
3691 if (strstr(activity, "busy")) {
3692 status_id = SIPE_STATUS_ID_BUSY;
3693 } else if (strstr(activity, "away")) {
3694 status_id = SIPE_STATUS_ID_AWAY;
3698 if (!status_id) {
3699 status_id = SIPE_STATUS_ID_AVAILABLE;
3702 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3703 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3704 } else {
3705 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
3708 g_free(activity);
3709 xmlnode_free(pidf);
3712 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3714 const char *availability;
3715 const char *activity;
3716 const char *display_name = NULL;
3717 const char *activity_name = NULL;
3718 const char *name;
3719 char *uri;
3720 int avl;
3721 int act;
3722 struct sipe_buddy *sbuddy;
3724 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3726 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3727 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3728 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3729 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3730 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3731 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3732 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
3733 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
3735 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3736 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3737 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3738 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3739 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3740 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3742 name = xmlnode_get_attrib(xn_presentity, "uri");
3743 uri = g_strdup_printf("sip:%s", name);
3744 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3745 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3747 // updating display name if alias was just URI
3748 if (xn_display_name) {
3749 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3750 GSList *entry = buddies;
3751 PurpleBuddy *p_buddy;
3752 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3754 while (entry) {
3755 const char *email_str, *server_alias;
3757 p_buddy = entry->data;
3759 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3760 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3761 purple_blist_alias_buddy(p_buddy, display_name);
3764 server_alias = purple_buddy_get_server_alias(p_buddy);
3765 if (display_name &&
3766 ( (server_alias && strcmp(display_name, server_alias))
3767 || !server_alias || strlen(server_alias) == 0 )
3769 purple_blist_server_alias_buddy(p_buddy, display_name);
3772 if (email) {
3773 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3774 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3775 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3779 entry = entry->next;
3783 avl = atoi(availability);
3784 act = atoi(activity);
3786 if(sip->msrtc_event_categories){
3787 if (act == 100 && avl == 0)
3788 activity_name = SIPE_STATUS_ID_OFFLINE;
3789 else if (act == 100 && avl == 300)
3790 activity_name = SIPE_STATUS_ID_AWAY;
3791 else if (act == 300 && avl == 300)
3792 activity_name = SIPE_STATUS_ID_BRB;
3793 else if (act == 400 && avl == 300)
3794 activity_name = SIPE_STATUS_ID_AVAILABLE;
3795 else if (act == 500 && act == 300)
3796 activity_name = SIPE_STATUS_ID_ONPHONE;
3797 else if (act == 600 && avl == 300)
3798 activity_name = SIPE_STATUS_ID_BUSY;
3799 else if (act == 0 && avl == 0){ //MSRTC elements are zero
3800 if(avail){ //Check for LegacyInterop elements
3801 avl = atoi(avail);
3802 if(avl == 18500)
3803 activity_name = SIPE_STATUS_ID_OFFLINE;
3804 else if (avl == 3500)
3805 activity_name = SIPE_STATUS_ID_AVAILABLE;
3806 else if (avl == 15500)
3807 activity_name = SIPE_STATUS_ID_AWAY;
3808 else if (avl == 6500)
3809 activity_name = SIPE_STATUS_ID_BUSY;
3810 else if (avl == 12500)
3811 activity_name = SIPE_STATUS_ID_BRB;
3816 if(activity_name == NULL){
3817 if (act <= 100)
3818 activity_name = SIPE_STATUS_ID_AWAY;
3819 else if (act <= 150)
3820 activity_name = SIPE_STATUS_ID_LUNCH;
3821 else if (act <= 300)
3822 activity_name = SIPE_STATUS_ID_BRB;
3823 else if (act <= 400)
3824 activity_name = SIPE_STATUS_ID_AVAILABLE;
3825 else if (act <= 500)
3826 activity_name = SIPE_STATUS_ID_ONPHONE;
3827 else if (act <= 600)
3828 activity_name = SIPE_STATUS_ID_BUSY;
3829 else
3830 activity_name = SIPE_STATUS_ID_AVAILABLE;
3832 if (avl == 0)
3833 activity_name = SIPE_STATUS_ID_OFFLINE;
3836 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3837 if (sbuddy)
3839 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3840 sbuddy->annotation = NULL;
3841 if (note) { sbuddy->annotation = g_strdup(note); }
3843 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3844 sbuddy->device_name = NULL;
3845 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3848 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3849 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3850 g_free(note);
3851 xmlnode_free(xn_presentity);
3852 g_free(uri);
3855 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3857 char *ctype = sipmsg_find_header(msg, "Content-Type");
3859 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3861 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3862 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3864 const char *content = msg->body;
3865 unsigned length = msg->bodylen;
3866 PurpleMimeDocument *mime = NULL;
3868 if (strstr(ctype, "multipart"))
3870 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3871 const char *content_type;
3872 GList* parts;
3873 mime = purple_mime_document_parse(doc);
3874 parts = purple_mime_document_get_parts(mime);
3875 while(parts) {
3876 content = purple_mime_part_get_data(parts->data);
3877 length = purple_mime_part_get_length(parts->data);
3878 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3879 if(content_type && strstr(content_type,"application/rlmi+xml"))
3881 process_incoming_notify_rlmi_resub(sip, content, length);
3883 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3885 process_incoming_notify_msrtc(sip, content, length);
3887 else
3889 process_incoming_notify_rlmi(sip, content, length);
3891 parts = parts->next;
3893 g_free(doc);
3895 if (mime)
3897 purple_mime_document_free(mime);
3900 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3902 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3904 else if(strstr(ctype, "application/rlmi+xml"))
3906 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3909 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3911 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3913 else
3915 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3919 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
3921 char *ctype = sipmsg_find_header(msg, "Content-Type");
3922 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
3924 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
3926 if (ctype &&
3927 strstr(ctype, "multipart") &&
3928 (strstr(ctype, "application/rlmi+xml") ||
3929 strstr(ctype, "application/msrtc-event-categories+xml"))) {
3930 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3931 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
3932 GList *parts = purple_mime_document_get_parts(mime);
3933 GSList *buddies = NULL;
3934 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3936 while (parts) {
3937 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
3938 purple_mime_part_get_length(parts->data));
3939 gchar *uri = g_strdup(xmlnode_get_attrib(xml, "uri"));
3941 if (strstr(uri, "sip:") == NULL) {
3942 gchar *tmp = uri;
3943 uri = g_strdup_printf("sip:%s", tmp);
3944 g_free(tmp);
3946 buddies = g_slist_append(buddies, uri);
3947 xmlnode_free(xml);
3949 parts = parts->next;
3951 g_free(doc);
3952 if (mime) purple_mime_document_free(mime);
3954 payload->host = who;
3955 payload->buddies = buddies;
3956 sipe_schedule_action(action_name, timeout,
3957 sipe_subscribe_presence_batched_routed,
3958 sipe_subscribe_presence_batched_routed_free,
3959 sip, payload);
3960 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
3962 } else {
3963 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
3964 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
3966 g_free(action_name);
3970 * Dispatcher for all incoming subscription information
3971 * whether it comes from NOTIFY, BENOTIFY requests or
3972 * piggy-backed to subscription's OK responce.
3974 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3975 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3977 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3979 gchar *event = sipmsg_find_header(msg, "Event");
3980 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3981 int timeout = 0;
3983 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3984 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3986 if (!request)
3988 const gchar *expires_header;
3989 expires_header = sipmsg_find_header(msg, "Expires");
3990 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3991 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
3992 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
3995 if (!subscription_state || strstr(subscription_state, "active"))
3997 if (event && !g_ascii_strcasecmp(event, "presence"))
3999 sipe_process_presence(sip, msg);
4001 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4003 sipe_process_roaming_contacts(sip, msg, NULL);
4005 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
4007 sipe_process_roaming_self(sip, msg);
4009 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4011 sipe_process_roaming_acl(sip, msg);
4013 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4015 sipe_process_presence_wpending(sip, msg);
4017 else
4019 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
4023 //The server sends a (BE)NOTIFY with the status 'terminated'
4024 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4025 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4026 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4027 g_free(from);
4030 if (timeout && event) {// For LSC 2005 and OCS 2007
4031 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4032 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4034 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4035 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4036 g_free(action_name);
4038 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4039 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4041 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4042 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4043 g_free(action_name);
4045 else*/
4046 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4047 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4049 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4050 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4051 g_free(action_name);
4053 else if (!g_ascii_strcasecmp(event, "presence") &&
4054 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4056 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4057 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4058 if(sip->batched_support) {
4059 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
4060 if(!g_ascii_strcasecmp(who, my_self)){
4061 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4062 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4063 g_free(who); /* unused */
4065 else {
4066 sipe_process_presence_timeout(sip, msg, who, timeout);
4068 g_free(my_self);
4070 else {
4071 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4072 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4074 g_free(action_name);
4075 /* "who" will be freed by the action we just scheduled */
4079 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4081 sipe_process_registration_notify(sip, msg);
4084 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4085 if (request && !benotify)
4087 sipmsg_remove_header(msg, "Expires");
4088 sipmsg_remove_header(msg, "subscription-state");
4089 sipmsg_remove_header(msg, "Event");
4090 sipmsg_remove_header(msg, "Require");
4091 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4096 * unused. Needed?
4098 static gchar* gen_xpidf(struct sipe_account_data *sip)
4100 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4101 "<presence>\r\n"
4102 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4103 "<display name=\"sip:%s\"/>\r\n"
4104 "<atom id=\"1234\">\r\n"
4105 "<address uri=\"sip:%s\">\r\n"
4106 "<status status=\"%s\"/>\r\n"
4107 "</address>\r\n"
4108 "</atom>\r\n"
4109 "</presence>\r\n",
4110 sip->username,
4111 sip->username,
4112 sip->username,
4113 sip->status);
4114 return doc;
4119 static gchar* gen_pidf(struct sipe_account_data *sip)
4121 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4122 "<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"
4123 "<tuple id=\"0\">\r\n"
4124 "<status>\r\n"
4125 "<basic>open</basic>\r\n"
4126 "<ep:activities>\r\n"
4127 " <ep:activity>%s</ep:activity>\r\n"
4128 "</ep:activities>"
4129 "</status>\r\n"
4130 "</tuple>\r\n"
4131 "<ci:display-name>%s</ci:display-name>\r\n"
4132 "</presence>",
4133 sip->username,
4134 sip->status,
4135 sip->username);
4136 return doc;
4140 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4142 int availability = 300; // online
4143 int activity = 400; // Available
4144 gchar *name;
4145 gchar *body;
4146 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4147 activity = 100;
4148 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4149 activity = 150;
4150 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4151 activity = 300;
4152 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4153 activity = 400;
4154 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4155 activity = 500;
4156 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4157 activity = 600;
4158 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4159 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4160 availability = 0; // offline
4161 activity = 100;
4162 } else {
4163 activity = 400; // available
4166 name = g_strdup_printf("sip: sip:%s", sip->username);
4167 //@TODO: send user data - state; add hostname in upper case
4168 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4169 send_soap_request_with_cb(sip, body, NULL , NULL);
4170 g_free(name);
4171 g_free(body);
4174 static gboolean
4175 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4177 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4178 if (msg->response == 200) {
4179 sip->status_version = 0;
4180 send_presence_status(sip);
4182 return TRUE;
4185 static gboolean
4186 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4188 if (msg->response == 409) {
4189 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4190 // TODO need to parse the version #'s?
4191 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4192 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4193 gchar *tmp;
4194 gchar *hdr;
4196 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4198 tmp = get_contact(sip);
4199 hdr = g_strdup_printf("Contact: %s\r\n"
4200 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4202 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4204 g_free(tmp);
4205 g_free(hdr);
4206 g_free(uri);
4207 g_free(doc);
4209 return TRUE;
4212 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4214 int code;
4215 gchar *uri;
4216 gchar *doc;
4217 gchar *tmp;
4218 gchar *hdr;
4219 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4220 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4221 code = 12000;
4222 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4223 code = 9000;
4224 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4225 code = 7500;
4226 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4227 code = 6000;
4228 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4229 code = 4500;
4230 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4231 code = 3000;
4232 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4233 code = 0;
4234 } else {
4235 // Offline or invisible
4236 code = 18000;
4239 uri = g_strdup_printf("sip:%s", sip->username);
4240 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4241 sip->status_version, code,
4242 sip->status_version, code,
4243 sip->status_version, note ? note : "",
4244 sip->status_version, note ? note : "",
4245 sip->status_version, note ? note : ""
4247 sip->status_version++;
4249 tmp = get_contact(sip);
4250 hdr = g_strdup_printf("Contact: %s\r\n"
4251 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4253 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4255 g_free(tmp);
4256 g_free(hdr);
4257 g_free(uri);
4258 g_free(doc);
4261 static void send_presence_status(struct sipe_account_data *sip)
4263 PurpleStatus * status = purple_account_get_active_status(sip->account);
4264 const gchar *note;
4265 if (!status) return;
4267 note = purple_status_get_attr_string(status, "message");
4269 if(sip->msrtc_event_categories){
4270 send_presence_category_publish(sip, note);
4271 } else {
4272 send_presence_soap(sip, note);
4276 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4278 gboolean found = FALSE;
4279 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4280 if (msg->response == 0) { /* request */
4281 if (!strcmp(msg->method, "MESSAGE")) {
4282 process_incoming_message(sip, msg);
4283 found = TRUE;
4284 } else if (!strcmp(msg->method, "NOTIFY")) {
4285 purple_debug_info("sipe","send->process_incoming_notify\n");
4286 process_incoming_notify(sip, msg, TRUE, FALSE);
4287 found = TRUE;
4288 } else if (!strcmp(msg->method, "BENOTIFY")) {
4289 purple_debug_info("sipe","send->process_incoming_benotify\n");
4290 process_incoming_notify(sip, msg, TRUE, TRUE);
4291 found = TRUE;
4292 } else if (!strcmp(msg->method, "INVITE")) {
4293 process_incoming_invite(sip, msg);
4294 found = TRUE;
4295 } else if (!strcmp(msg->method, "OPTIONS")) {
4296 process_incoming_options(sip, msg);
4297 found = TRUE;
4298 } else if (!strcmp(msg->method, "INFO")) {
4299 // TODO needs work
4300 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4301 if (from) {
4302 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4304 g_free(from);
4305 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4306 found = TRUE;
4307 } else if (!strcmp(msg->method, "ACK")) {
4308 // ACK's don't need any response
4309 found = TRUE;
4310 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4311 // LCS 2005 sends us these - just respond 200 OK
4312 found = TRUE;
4313 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4314 } else if (!strcmp(msg->method, "BYE")) {
4315 struct sip_im_session *session;
4316 gchar *from;
4317 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4319 from = parse_from(sipmsg_find_header(msg, "From"));
4320 session = find_im_session (sip, from);
4321 g_free(from);
4323 if (session) {
4324 // TODO Let the user know the other user left the conversation?
4325 im_session_destroy(sip, session);
4328 found = TRUE;
4329 } else {
4330 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4332 } else { /* response */
4333 struct transaction *trans = transactions_find(sip, msg);
4334 if (trans) {
4335 if (msg->response == 407) {
4336 gchar *resend, *auth, *ptmp;
4338 if (sip->proxy.retries > 30) return;
4339 sip->proxy.retries++;
4340 /* do proxy authentication */
4342 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4344 fill_auth(sip, ptmp, &sip->proxy);
4345 auth = auth_header(sip, &sip->proxy, trans->msg);
4346 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4347 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4348 g_free(auth);
4349 resend = sipmsg_to_string(trans->msg);
4350 /* resend request */
4351 sendout_pkt(sip->gc, resend);
4352 g_free(resend);
4353 } else {
4354 if (msg->response == 100 || msg->response == 180) {
4355 /* ignore provisional response */
4356 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4357 } else {
4358 sip->proxy.retries = 0;
4359 if (!strcmp(trans->msg->method, "REGISTER")) {
4360 if (msg->response == 401)
4362 sip->registrar.retries++;
4363 sip->registrar.expires = 0;
4365 else
4367 sip->registrar.retries = 0;
4369 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4370 } else {
4371 if (msg->response == 401) {
4372 gchar *resend, *auth, *ptmp;
4374 if (sip->registrar.retries > 4) return;
4375 sip->registrar.retries++;
4377 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4378 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4379 } else {
4380 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4383 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4385 fill_auth(sip, ptmp, &sip->registrar);
4386 auth = auth_header(sip, &sip->registrar, trans->msg);
4387 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4388 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
4390 //sipmsg_remove_header(trans->msg, "Authorization");
4391 //sipmsg_add_header(trans->msg, "Authorization", auth);
4392 g_free(auth);
4393 resend = sipmsg_to_string(trans->msg);
4394 /* resend request */
4395 sendout_pkt(sip->gc, resend);
4396 g_free(resend);
4400 if (trans->callback) {
4401 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4402 /* call the callback to process response*/
4403 (trans->callback)(sip, msg, trans);
4405 /* Not sure if this is needed or what needs to be done
4406 but transactions seem to be removed prematurely so
4407 this only removes them if the response is 200 OK */
4408 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4409 /*Has a bug and it's unneccesary*/
4410 /*transactions_remove(sip, trans);*/
4414 found = TRUE;
4415 } else {
4416 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4419 if (!found) {
4420 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4424 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4426 char *cur;
4427 char *dummy;
4428 struct sipmsg *msg;
4429 int restlen;
4430 cur = conn->inbuf;
4432 /* according to the RFC remove CRLF at the beginning */
4433 while (*cur == '\r' || *cur == '\n') {
4434 cur++;
4436 if (cur != conn->inbuf) {
4437 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4438 conn->inbufused = strlen(conn->inbuf);
4441 /* Received a full Header? */
4442 sip->processing_input = TRUE;
4443 while (sip->processing_input &&
4444 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4445 time_t currtime = time(NULL);
4446 cur += 2;
4447 cur[0] = '\0';
4448 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4449 msg = sipmsg_parse_header(conn->inbuf);
4450 cur[0] = '\r';
4451 cur += 2;
4452 restlen = conn->inbufused - (cur - conn->inbuf);
4453 if (restlen >= msg->bodylen) {
4454 dummy = g_malloc(msg->bodylen + 1);
4455 memcpy(dummy, cur, msg->bodylen);
4456 dummy[msg->bodylen] = '\0';
4457 msg->body = dummy;
4458 cur += msg->bodylen;
4459 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4460 conn->inbufused = strlen(conn->inbuf);
4461 } else {
4462 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4463 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4464 sipmsg_free(msg);
4465 return;
4468 /*if (msg->body) {
4469 purple_debug_info("sipe", "body:\n%s", msg->body);
4472 // Verify the signature before processing it
4473 if (sip->registrar.gssapi_context) {
4474 struct sipmsg_breakdown msgbd;
4475 gchar *signature_input_str;
4476 gchar *rspauth;
4477 msgbd.msg = msg;
4478 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4479 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4481 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4483 if (rspauth != NULL) {
4484 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
4485 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4486 process_input_message(sip, msg);
4487 } else {
4488 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
4489 purple_connection_error(sip->gc, _("Invalid message signature received"));
4490 sip->gc->wants_to_die = TRUE;
4492 } else if (msg->response == 401) {
4493 purple_connection_error(sip->gc, _("Wrong Password"));
4494 sip->gc->wants_to_die = TRUE;
4496 g_free(signature_input_str);
4498 g_free(rspauth);
4499 sipmsg_breakdown_free(&msgbd);
4500 } else {
4501 process_input_message(sip, msg);
4504 sipmsg_free(msg);
4508 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4510 PurpleConnection *gc = data;
4511 struct sipe_account_data *sip = gc->proto_data;
4512 struct sipmsg *msg;
4513 int len;
4514 time_t currtime;
4516 static char buffer[65536];
4517 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4518 buffer[len] = '\0';
4519 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4520 msg = sipmsg_parse_msg(buffer);
4521 if (msg) process_input_message(sip, msg);
4525 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4527 struct sipe_account_data *sip = gc->proto_data;
4528 PurpleSslConnection *gsc = sip->gsc;
4530 purple_debug_error("sipe", "%s",debug);
4531 purple_connection_error(gc, msg);
4533 /* Invalidate this connection. Next send will open a new one */
4534 if (gsc) {
4535 connection_remove(sip, gsc->fd);
4536 purple_ssl_close(gsc);
4538 sip->gsc = NULL;
4539 sip->fd = -1;
4542 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4544 PurpleConnection *gc = data;
4545 struct sipe_account_data *sip;
4546 struct sip_connection *conn;
4547 int readlen, len;
4548 gboolean firstread = TRUE;
4550 /* NOTE: This check *IS* necessary */
4551 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4552 purple_ssl_close(gsc);
4553 return;
4556 sip = gc->proto_data;
4557 conn = connection_find(sip, gsc->fd);
4558 if (conn == NULL) {
4559 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4560 gc->wants_to_die = TRUE;
4561 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4562 return;
4565 /* Read all available data from the SSL connection */
4566 do {
4567 /* Increase input buffer size as needed */
4568 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4569 conn->inbuflen += SIMPLE_BUF_INC;
4570 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4571 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4574 /* Try to read as much as there is space left in the buffer */
4575 readlen = conn->inbuflen - conn->inbufused - 1;
4576 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4578 if (len < 0 && errno == EAGAIN) {
4579 /* Try again later */
4580 return;
4581 } else if (len < 0) {
4582 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4583 return;
4584 } else if (firstread && (len == 0)) {
4585 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4586 return;
4589 conn->inbufused += len;
4590 firstread = FALSE;
4592 /* Equivalence indicates that there is possibly more data to read */
4593 } while (len == readlen);
4595 conn->inbuf[conn->inbufused] = '\0';
4596 process_input(sip, conn);
4600 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4602 PurpleConnection *gc = data;
4603 struct sipe_account_data *sip = gc->proto_data;
4604 int len;
4605 struct sip_connection *conn = connection_find(sip, source);
4606 if (!conn) {
4607 purple_debug_error("sipe", "Connection not found!\n");
4608 return;
4611 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4612 conn->inbuflen += SIMPLE_BUF_INC;
4613 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4616 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4618 if (len < 0 && errno == EAGAIN)
4619 return;
4620 else if (len <= 0) {
4621 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4622 connection_remove(sip, source);
4623 if (sip->fd == source) sip->fd = -1;
4624 return;
4627 conn->inbufused += len;
4628 conn->inbuf[conn->inbufused] = '\0';
4630 process_input(sip, conn);
4633 /* Callback for new connections on incoming TCP port */
4634 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4636 PurpleConnection *gc = data;
4637 struct sipe_account_data *sip = gc->proto_data;
4638 struct sip_connection *conn;
4640 int newfd = accept(source, NULL, NULL);
4642 conn = connection_create(sip, newfd);
4644 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4647 static void login_cb(gpointer data, gint source, const gchar *error_message)
4649 PurpleConnection *gc = data;
4650 struct sipe_account_data *sip;
4651 struct sip_connection *conn;
4653 if (!PURPLE_CONNECTION_IS_VALID(gc))
4655 if (source >= 0)
4656 close(source);
4657 return;
4660 if (source < 0) {
4661 purple_connection_error(gc, _("Could not connect"));
4662 return;
4665 sip = gc->proto_data;
4666 sip->fd = source;
4667 sip->last_keepalive = time(NULL);
4669 conn = connection_create(sip, source);
4671 do_register(sip);
4673 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4676 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4678 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4679 if (sip == NULL) return;
4681 do_register(sip);
4684 static guint sipe_ht_hash_nick(const char *nick)
4686 char *lc = g_utf8_strdown(nick, -1);
4687 guint bucket = g_str_hash(lc);
4688 g_free(lc);
4690 return bucket;
4693 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4695 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4698 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4700 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4702 sip->listen_data = NULL;
4704 if (listenfd == -1) {
4705 purple_connection_error(sip->gc, _("Could not create listen socket"));
4706 return;
4709 sip->fd = listenfd;
4711 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4712 sip->listenfd = sip->fd;
4714 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4716 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4717 do_register(sip);
4720 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4722 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4723 int addr_size;
4725 sip->query_data = NULL;
4727 if (!hosts || !hosts->data) {
4728 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4729 return;
4732 addr_size = GPOINTER_TO_INT(hosts->data);
4733 hosts = g_slist_remove(hosts, hosts->data);
4734 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4735 g_free(hosts->data);
4736 hosts = g_slist_remove(hosts, hosts->data);
4737 while (hosts) {
4738 hosts = g_slist_remove(hosts, hosts->data);
4739 g_free(hosts->data);
4740 hosts = g_slist_remove(hosts, hosts->data);
4743 /* create socket for incoming connections */
4744 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4745 sipe_udp_host_resolved_listen_cb, sip);
4746 if (sip->listen_data == NULL) {
4747 purple_connection_error(sip->gc, _("Could not create listen socket"));
4748 return;
4752 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4753 gpointer data)
4755 PurpleConnection *gc = data;
4756 struct sipe_account_data *sip;
4758 /* If the connection is already disconnected, we don't need to do anything else */
4759 if (!PURPLE_CONNECTION_IS_VALID(gc))
4760 return;
4762 sip = gc->proto_data;
4763 sip->fd = -1;
4764 sip->gsc = NULL;
4766 switch(error) {
4767 case PURPLE_SSL_CONNECT_FAILED:
4768 purple_connection_error(gc, _("Connection Failed"));
4769 break;
4770 case PURPLE_SSL_HANDSHAKE_FAILED:
4771 purple_connection_error(gc, _("SSL Handshake Failed"));
4772 break;
4773 case PURPLE_SSL_CERTIFICATE_INVALID:
4774 purple_connection_error(gc, _("SSL Certificate Invalid"));
4775 break;
4779 static void
4780 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4782 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4783 PurpleProxyConnectData *connect_data;
4785 sip->listen_data = NULL;
4787 sip->listenfd = listenfd;
4788 if (sip->listenfd == -1) {
4789 purple_connection_error(sip->gc, _("Could not create listen socket"));
4790 return;
4793 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4794 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4795 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4796 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4797 sipe_newconn_cb, sip->gc);
4798 purple_debug_info("sipe", "connecting to %s port %d\n",
4799 sip->realhostname, sip->realport);
4800 /* open tcp connection to the server */
4801 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4802 sip->realport, login_cb, sip->gc);
4804 if (connect_data == NULL) {
4805 purple_connection_error(sip->gc, _("Couldn't create socket"));
4810 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4812 PurpleAccount *account = sip->account;
4813 PurpleConnection *gc = sip->gc;
4815 if (purple_account_get_bool(account, "useport", FALSE)) {
4816 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4817 port = purple_account_get_int(account, "port", 0);
4818 } else {
4819 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4822 sip->realhostname = hostname;
4823 sip->realport = port;
4825 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4826 hostname, port);
4828 /* TODO: is there a good default grow size? */
4829 if (sip->transport != SIPE_TRANSPORT_UDP)
4830 sip->txbuf = purple_circ_buffer_new(0);
4832 if (sip->transport == SIPE_TRANSPORT_TLS) {
4833 /* SSL case */
4834 if (!purple_ssl_is_supported()) {
4835 gc->wants_to_die = TRUE;
4836 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4837 return;
4840 purple_debug_info("sipe", "using SSL\n");
4842 sip->gsc = purple_ssl_connect(account, hostname, port,
4843 login_cb_ssl, sipe_ssl_connect_failure, gc);
4844 if (sip->gsc == NULL) {
4845 purple_connection_error(gc, _("Could not create SSL context"));
4846 return;
4848 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4849 /* UDP case */
4850 purple_debug_info("sipe", "using UDP\n");
4852 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4853 if (sip->query_data == NULL) {
4854 purple_connection_error(gc, _("Could not resolve hostname"));
4856 } else {
4857 /* TCP case */
4858 purple_debug_info("sipe", "using TCP\n");
4859 /* create socket for incoming connections */
4860 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4861 sipe_tcp_connect_listen_cb, sip);
4862 if (sip->listen_data == NULL) {
4863 purple_connection_error(gc, _("Could not create listen socket"));
4864 return;
4869 /* Service list for autodection */
4870 static const struct sipe_service_data service_autodetect[] = {
4871 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4872 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4873 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4874 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4875 { NULL, NULL, 0 }
4878 /* Service list for SSL/TLS */
4879 static const struct sipe_service_data service_tls[] = {
4880 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4881 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4882 { NULL, NULL, 0 }
4885 /* Service list for TCP */
4886 static const struct sipe_service_data service_tcp[] = {
4887 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4888 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4889 { NULL, NULL, 0 }
4892 /* Service list for UDP */
4893 static const struct sipe_service_data service_udp[] = {
4894 { "sip", "udp", SIPE_TRANSPORT_UDP },
4895 { NULL, NULL, 0 }
4898 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4899 static void resolve_next_service(struct sipe_account_data *sip,
4900 const struct sipe_service_data *start)
4902 if (start) {
4903 sip->service_data = start;
4904 } else {
4905 sip->service_data++;
4906 if (sip->service_data->service == NULL) {
4907 gchar *hostname;
4908 /* Try connecting to the SIP hostname directly */
4909 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4910 if (sip->auto_transport) {
4911 // If SSL is supported, default to using it; OCS servers aren't configured
4912 // by default to accept TCP
4913 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4914 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4915 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4918 hostname = g_strdup(sip->sipdomain);
4919 create_connection(sip, hostname, 0);
4920 return;
4924 /* Try to resolve next service */
4925 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4926 sip->service_data->transport,
4927 sip->sipdomain,
4928 srvresolved, sip);
4931 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4933 struct sipe_account_data *sip = data;
4935 sip->srv_query_data = NULL;
4937 /* find the host to connect to */
4938 if (results) {
4939 gchar *hostname = g_strdup(resp->hostname);
4940 int port = resp->port;
4941 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4942 hostname, port);
4943 g_free(resp);
4945 sip->transport = sip->service_data->type;
4947 create_connection(sip, hostname, port);
4948 } else {
4949 resolve_next_service(sip, NULL);
4953 static void sipe_login(PurpleAccount *account)
4955 PurpleConnection *gc;
4956 struct sipe_account_data *sip;
4957 gchar **signinname_login, **userserver, **domain_user, **user_realm;
4958 const char *transport;
4960 const char *username = purple_account_get_username(account);
4961 gc = purple_account_get_connection(account);
4963 if (strpbrk(username, "\t\v\r\n") != NULL) {
4964 gc->wants_to_die = TRUE;
4965 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
4966 return;
4969 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4970 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4971 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4972 sip->gc = gc;
4973 sip->account = account;
4974 sip->reregister_set = FALSE;
4975 sip->reauthenticate_set = FALSE;
4976 sip->subscribed = FALSE;
4977 sip->subscribed_buddies = FALSE;
4979 signinname_login = g_strsplit(username, ",", 2);
4981 userserver = g_strsplit(signinname_login[0], "@", 2);
4982 purple_connection_set_display_name(gc, userserver[0]);
4983 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4984 sip->sipdomain = g_strdup(userserver[1]);
4986 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
4987 gc->wants_to_die = TRUE;
4988 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4989 return;
4992 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4993 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4994 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4996 user_realm = g_strsplit(sip->authuser, "@", 2);
4997 if (user_realm && user_realm[1]) {
4998 sip->authuser = g_strdup(user_realm[0]);
4999 sip->authdomain = g_strdup(user_realm[1]);
5002 sip->password = g_strdup(purple_connection_get_password(gc));
5004 g_strfreev(user_realm);
5005 g_strfreev(userserver);
5006 g_strfreev(domain_user);
5007 g_strfreev(signinname_login);
5009 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5011 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5013 /* TODO: Set the status correctly. */
5014 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5016 transport = purple_account_get_string(account, "transport", "auto");
5017 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
5018 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
5019 SIPE_TRANSPORT_UDP;
5021 if (purple_account_get_bool(account, "useproxy", FALSE)) {
5022 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
5023 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
5024 } else if (strcmp(transport, "auto") == 0) {
5025 sip->auto_transport = TRUE;
5026 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5027 } else if (strcmp(transport, "tls") == 0) {
5028 resolve_next_service(sip, service_tls);
5029 } else if (strcmp(transport, "tcp") == 0) {
5030 resolve_next_service(sip, service_tcp);
5031 } else {
5032 resolve_next_service(sip, service_udp);
5036 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5038 connection_free_all(sip);
5040 g_free(sip->epid);
5041 sip->epid = NULL;
5043 if (sip->query_data != NULL)
5044 purple_dnsquery_destroy(sip->query_data);
5045 sip->query_data = NULL;
5047 if (sip->srv_query_data != NULL)
5048 purple_srv_cancel(sip->srv_query_data);
5049 sip->srv_query_data = NULL;
5051 if (sip->listen_data != NULL)
5052 purple_network_listen_cancel(sip->listen_data);
5053 sip->listen_data = NULL;
5055 if (sip->gsc != NULL)
5056 purple_ssl_close(sip->gsc);
5057 sip->gsc = NULL;
5059 sipe_auth_free(&sip->registrar);
5060 sipe_auth_free(&sip->proxy);
5062 if (sip->txbuf)
5063 purple_circ_buffer_destroy(sip->txbuf);
5064 sip->txbuf = NULL;
5066 g_free(sip->realhostname);
5067 sip->realhostname = NULL;
5069 if (sip->listenpa)
5070 purple_input_remove(sip->listenpa);
5071 sip->listenpa = 0;
5072 if (sip->tx_handler)
5073 purple_input_remove(sip->tx_handler);
5074 sip->tx_handler = 0;
5075 if (sip->resendtimeout)
5076 purple_timeout_remove(sip->resendtimeout);
5077 sip->resendtimeout = 0;
5078 if (sip->timeouts) {
5079 GSList *entry = sip->timeouts;
5080 while (entry) {
5081 struct scheduled_action *sched_action = entry->data;
5082 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5083 purple_timeout_remove(sched_action->timeout_handler);
5084 (*sched_action->destroy)(sched_action->payload);
5085 g_free(sched_action->name);
5086 g_free(sched_action);
5087 entry = entry->next;
5090 g_slist_free(sip->timeouts);
5092 if (sip->allow_events) {
5093 GSList *entry = sip->allow_events;
5094 while (entry) {
5095 g_free(entry->data);
5096 entry = entry->next;
5099 g_slist_free(sip->allow_events);
5101 if (sip->contact)
5102 g_free(sip->contact);
5103 sip->contact = NULL;
5104 if (sip->regcallid)
5105 g_free(sip->regcallid);
5106 sip->regcallid = NULL;
5108 sip->fd = -1;
5109 sip->processing_input = FALSE;
5113 * A callback for g_hash_table_foreach_remove
5115 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
5117 sipe_free_buddy(buddy);
5120 static void sipe_close(PurpleConnection *gc)
5122 struct sipe_account_data *sip = gc->proto_data;
5124 if (sip) {
5125 /* leave all conversations */
5126 im_session_close_all(sip);
5128 /* unregister */
5129 do_register_exp(sip, 0);
5131 sipe_connection_cleanup(sip);
5132 g_free(sip->sipdomain);
5133 g_free(sip->username);
5134 g_free(sip->password);
5135 g_free(sip->authdomain);
5136 g_free(sip->authuser);
5137 g_free(sip->status);
5139 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
5140 g_hash_table_destroy(sip->buddies);
5142 g_free(gc->proto_data);
5143 gc->proto_data = NULL;
5146 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
5148 PurpleAccount *acct = purple_connection_get_account(gc);
5149 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
5150 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5151 if (conv == NULL)
5152 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5153 purple_conversation_present(conv);
5154 g_free(id);
5157 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5160 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5161 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
5164 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5166 PurpleNotifySearchResults *results;
5167 PurpleNotifySearchColumn *column;
5168 xmlnode *searchResults;
5169 xmlnode *mrow;
5170 int match_count = 0;
5171 gboolean more = FALSE;
5172 gchar *secondary;
5174 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5176 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5177 if (!searchResults) {
5178 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5179 return FALSE;
5182 results = purple_notify_searchresults_new();
5184 if (results == NULL) {
5185 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5186 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5188 xmlnode_free(searchResults);
5189 return FALSE;
5192 column = purple_notify_searchresults_column_new(_("User Name"));
5193 purple_notify_searchresults_column_add(results, column);
5195 column = purple_notify_searchresults_column_new(_("Name"));
5196 purple_notify_searchresults_column_add(results, column);
5198 column = purple_notify_searchresults_column_new(_("Company"));
5199 purple_notify_searchresults_column_add(results, column);
5201 column = purple_notify_searchresults_column_new(_("Country"));
5202 purple_notify_searchresults_column_add(results, column);
5204 column = purple_notify_searchresults_column_new(_("Email"));
5205 purple_notify_searchresults_column_add(results, column);
5207 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5208 GList *row = NULL;
5210 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5211 row = g_list_append(row, g_strdup(uri_parts[1]));
5212 g_strfreev(uri_parts);
5214 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5215 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5216 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5217 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5219 purple_notify_searchresults_row_add(results, row);
5220 match_count++;
5223 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5224 char *data = xmlnode_get_data_unescaped(mrow);
5225 more = (g_strcasecmp(data, "true") == 0);
5226 g_free(data);
5229 secondary = g_strdup_printf(
5230 dngettext(GETTEXT_PACKAGE,
5231 "Found %d contact%s:",
5232 "Found %d contacts%s:", match_count),
5233 match_count, more ? _(" (more matched your query)") : "");
5235 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5236 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5237 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5239 g_free(secondary);
5240 xmlnode_free(searchResults);
5241 return TRUE;
5244 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5246 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5247 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5248 unsigned i = 0;
5250 do {
5251 PurpleRequestField *field = entries->data;
5252 const char *id = purple_request_field_get_id(field);
5253 const char *value = purple_request_field_string_get_value(field);
5255 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5257 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5258 } while ((entries = g_list_next(entries)) != NULL);
5259 attrs[i] = NULL;
5261 if (i > 0) {
5262 gchar *query = g_strjoinv(NULL, attrs);
5263 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5264 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5265 send_soap_request_with_cb(gc->proto_data, body,
5266 (TransCallback) process_search_contact_response, NULL);
5267 g_free(body);
5268 g_free(query);
5271 g_strfreev(attrs);
5274 static void sipe_show_find_contact(PurplePluginAction *action)
5276 PurpleConnection *gc = (PurpleConnection *) action->context;
5277 PurpleRequestFields *fields;
5278 PurpleRequestFieldGroup *group;
5279 PurpleRequestField *field;
5281 fields = purple_request_fields_new();
5282 group = purple_request_field_group_new(NULL);
5283 purple_request_fields_add_group(fields, group);
5285 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5286 purple_request_field_group_add_field(group, field);
5287 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5288 purple_request_field_group_add_field(group, field);
5289 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5290 purple_request_field_group_add_field(group, field);
5291 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5292 purple_request_field_group_add_field(group, field);
5294 purple_request_fields(gc,
5295 _("Search"),
5296 _("Search for a Contact"),
5297 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5298 fields,
5299 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5300 _("_Cancel"), NULL,
5301 purple_connection_get_account(gc), NULL, NULL, gc);
5304 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5306 GList *menu = NULL;
5307 PurplePluginAction *act;
5309 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5310 menu = g_list_prepend(menu, act);
5312 menu = g_list_reverse(menu);
5314 return menu;
5317 static void dummy_permit_deny(PurpleConnection *gc)
5321 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5323 return TRUE;
5327 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5329 return TRUE;
5333 static char *sipe_status_text(PurpleBuddy *buddy)
5335 struct sipe_account_data *sip;
5336 struct sipe_buddy *sbuddy;
5337 char *text = NULL;
5339 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5340 if (sip) //happens on pidgin exit
5342 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5343 if (sbuddy && sbuddy->annotation)
5345 text = g_strdup(sbuddy->annotation);
5349 return text;
5352 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5354 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5355 const PurpleStatus *status = purple_presence_get_active_status(presence);
5356 struct sipe_account_data *sip;
5357 struct sipe_buddy *sbuddy;
5358 char *annotation = NULL;
5360 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5361 if (sip) //happens on pidgin exit
5363 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5364 if (sbuddy)
5366 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5370 //Layout
5371 if (purple_presence_is_online(presence))
5373 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5376 if (annotation)
5378 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5379 g_free(annotation);
5384 static GHashTable *
5385 sipe_get_account_text_table(PurpleAccount *account)
5387 GHashTable *table;
5388 table = g_hash_table_new(g_str_hash, g_str_equal);
5389 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5390 return table;
5393 static PurpleBuddy *
5394 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5396 PurpleBuddy *clone;
5397 const gchar *server_alias, *email;
5398 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5400 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5402 purple_blist_add_buddy(clone, NULL, group, NULL);
5404 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5405 if (server_alias) {
5406 purple_blist_server_alias_buddy(clone, server_alias);
5409 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5410 if (email) {
5411 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5414 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5415 //for UI to update;
5416 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5417 return clone;
5420 static void
5421 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5423 PurpleBuddy *buddy, *b;
5424 PurpleConnection *gc;
5425 PurpleGroup * group = purple_find_group(group_name);
5427 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5429 buddy = (PurpleBuddy *)node;
5431 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5432 gc = purple_account_get_connection(buddy->account);
5434 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5435 if (!b){
5436 b = purple_blist_add_buddy_clone(group, buddy);
5439 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5442 static void
5443 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5445 const gchar *email;
5446 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5448 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5449 if (email)
5451 char *mailto = g_strdup_printf("mailto:%s", email);
5452 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5453 #ifndef _WIN32
5455 pid_t pid;
5456 char *const parmList[] = {mailto, NULL};
5457 if ((pid = fork()) == -1)
5459 purple_debug_info("sipe", "fork() error\n");
5461 else if (pid == 0)
5463 execvp("xdg-email", parmList);
5464 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5467 #else
5469 BOOL ret;
5470 _flushall();
5471 errno = 0;
5472 //@TODO resolve env variable %WINDIR% first
5473 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5474 if (errno)
5476 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5479 #endif
5481 g_free(mailto);
5483 else
5485 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5490 * A menu which appear when right-clicking on buddy in contact list.
5492 static GList *
5493 sipe_buddy_menu(PurpleBuddy *buddy)
5495 PurpleBlistNode *g_node;
5496 PurpleGroup *group, *gr_parent;
5497 PurpleMenuAction *act;
5498 GList *menu = NULL;
5499 GList *menu_groups = NULL;
5501 act = purple_menu_action_new(_("Send Email..."),
5502 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5503 NULL, NULL);
5504 menu = g_list_prepend(menu, act);
5506 gr_parent = purple_buddy_get_group(buddy);
5507 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5508 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5509 continue;
5511 group = (PurpleGroup *)g_node;
5512 if (group == gr_parent)
5513 continue;
5515 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5516 continue;
5518 act = purple_menu_action_new(purple_group_get_name(group),
5519 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5520 group->name, NULL);
5521 menu_groups = g_list_prepend(menu_groups, act);
5523 menu_groups = g_list_reverse(menu_groups);
5525 act = purple_menu_action_new(_("Copy to"),
5526 NULL,
5527 NULL, menu_groups);
5528 menu = g_list_prepend(menu, act);
5529 menu = g_list_reverse(menu);
5531 return menu;
5534 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5535 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5536 return sipe_buddy_menu((PurpleBuddy *) node);
5537 } else {
5538 return NULL;
5542 static gboolean
5543 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5545 gboolean ret = TRUE;
5546 char *username = (char *)trans->payload;
5548 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5549 PurpleBuddy *pbuddy;
5550 struct sipe_buddy *sbuddy;
5551 const char *alias;
5552 char *server_alias = NULL;
5553 char *email = NULL;
5554 const char *device_name = NULL;
5556 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5558 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5559 alias = purple_buddy_get_local_alias(pbuddy);
5561 if (sip)
5563 //will query buddy UA's capabilities and send answer to log
5564 sipe_options_request(sip, username);
5566 sbuddy = g_hash_table_lookup(sip->buddies, username);
5567 if (sbuddy)
5569 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5573 if (msg->response != 200) {
5574 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5575 } else {
5576 xmlnode *searchResults;
5577 xmlnode *mrow;
5579 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5580 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5581 if (!searchResults) {
5582 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5583 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5584 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5585 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5586 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5587 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5588 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5589 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5590 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5591 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5592 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5593 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5594 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5595 if (!email || strcmp("", email)) {
5596 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5597 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5601 xmlnode_free(searchResults);
5604 purple_notify_user_info_add_section_break(info);
5606 if (!server_alias || !strcmp("", server_alias)) {
5607 g_free(server_alias);
5608 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5609 if (server_alias) {
5610 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5614 // same as server alias, do not present
5615 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5616 if (alias)
5618 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5621 if (!email || !strcmp("", email)) {
5622 g_free(email);
5623 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5624 if (email) {
5625 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5629 if (device_name)
5631 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5634 /* show a buddy's user info in a nice dialog box */
5635 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5636 username, /* buddy's username */
5637 info, /* body */
5638 NULL, /* callback called when dialog closed */
5639 NULL); /* userdata for callback */
5641 return ret;
5645 * AD search first, LDAP based
5647 static void sipe_get_info(PurpleConnection *gc, const char *username)
5649 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5650 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5652 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5653 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5654 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5655 g_free(body);
5656 g_free(row);
5659 static PurplePlugin *my_protocol = NULL;
5661 static PurplePluginProtocolInfo prpl_info =
5664 NULL, /* user_splits */
5665 NULL, /* protocol_options */
5666 NO_BUDDY_ICONS, /* icon_spec */
5667 sipe_list_icon, /* list_icon */
5668 NULL, /* list_emblems */
5669 sipe_status_text, /* status_text */
5670 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5671 sipe_status_types, /* away_states */
5672 sipe_blist_node_menu, /* blist_node_menu */
5673 NULL, /* chat_info */
5674 NULL, /* chat_info_defaults */
5675 sipe_login, /* login */
5676 sipe_close, /* close */
5677 sipe_im_send, /* send_im */
5678 NULL, /* set_info */ // TODO maybe
5679 sipe_send_typing, /* send_typing */
5680 sipe_get_info, /* get_info */
5681 sipe_set_status, /* set_status */
5682 NULL, /* set_idle */
5683 NULL, /* change_passwd */
5684 sipe_add_buddy, /* add_buddy */
5685 NULL, /* add_buddies */
5686 sipe_remove_buddy, /* remove_buddy */
5687 NULL, /* remove_buddies */
5688 sipe_add_permit, /* add_permit */
5689 sipe_add_deny, /* add_deny */
5690 sipe_add_deny, /* rem_permit */
5691 sipe_add_permit, /* rem_deny */
5692 dummy_permit_deny, /* set_permit_deny */
5693 NULL, /* join_chat */
5694 NULL, /* reject_chat */
5695 NULL, /* get_chat_name */
5696 NULL, /* chat_invite */
5697 NULL, /* chat_leave */
5698 NULL, /* chat_whisper */
5699 NULL, /* chat_send */
5700 sipe_keep_alive, /* keepalive */
5701 NULL, /* register_user */
5702 NULL, /* get_cb_info */ // deprecated
5703 NULL, /* get_cb_away */ // deprecated
5704 sipe_alias_buddy, /* alias_buddy */
5705 sipe_group_buddy, /* group_buddy */
5706 sipe_rename_group, /* rename_group */
5707 NULL, /* buddy_free */
5708 sipe_convo_closed, /* convo_closed */
5709 purple_normalize_nocase, /* normalize */
5710 NULL, /* set_buddy_icon */
5711 sipe_remove_group, /* remove_group */
5712 NULL, /* get_cb_real_name */ // TODO?
5713 NULL, /* set_chat_topic */
5714 NULL, /* find_blist_chat */
5715 NULL, /* roomlist_get_list */
5716 NULL, /* roomlist_cancel */
5717 NULL, /* roomlist_expand_category */
5718 NULL, /* can_receive_file */
5719 NULL, /* send_file */
5720 NULL, /* new_xfer */
5721 NULL, /* offline_message */
5722 NULL, /* whiteboard_prpl_ops */
5723 sipe_send_raw, /* send_raw */
5724 NULL, /* roomlist_room_serialize */
5725 NULL, /* unregister_user */
5726 NULL, /* send_attention */
5727 NULL, /* get_attention_types */
5729 sizeof(PurplePluginProtocolInfo), /* struct_size */
5730 sipe_get_account_text_table, /* get_account_text_table */
5734 static PurplePluginInfo info = {
5735 PURPLE_PLUGIN_MAGIC,
5736 PURPLE_MAJOR_VERSION,
5737 PURPLE_MINOR_VERSION,
5738 PURPLE_PLUGIN_PROTOCOL, /**< type */
5739 NULL, /**< ui_requirement */
5740 0, /**< flags */
5741 NULL, /**< dependencies */
5742 PURPLE_PRIORITY_DEFAULT, /**< priority */
5743 "prpl-sipe", /**< id */
5744 "Microsoft LCS/OCS", /**< name */
5745 VERSION, /**< version */
5746 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5747 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5748 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5749 "Gabriel Burt <gburt@novell.com>", /**< author */
5750 PURPLE_WEBSITE, /**< homepage */
5751 sipe_plugin_load, /**< load */
5752 sipe_plugin_unload, /**< unload */
5753 sipe_plugin_destroy, /**< destroy */
5754 NULL, /**< ui_info */
5755 &prpl_info, /**< extra_info */
5756 NULL,
5757 sipe_actions,
5758 NULL,
5759 NULL,
5760 NULL,
5761 NULL
5764 static void sipe_plugin_destroy(PurplePlugin *plugin)
5766 GList *entry;
5768 entry = prpl_info.protocol_options;
5769 while (entry) {
5770 purple_account_option_destroy(entry->data);
5771 entry = g_list_delete_link(entry, entry);
5773 prpl_info.protocol_options = NULL;
5775 entry = prpl_info.user_splits;
5776 while (entry) {
5777 purple_account_user_split_destroy(entry->data);
5778 entry = g_list_delete_link(entry, entry);
5780 prpl_info.user_splits = NULL;
5783 static void init_plugin(PurplePlugin *plugin)
5785 PurpleAccountUserSplit *split;
5786 PurpleAccountOption *option;
5788 #ifdef ENABLE_NLS
5789 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5790 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5791 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5792 #endif
5794 purple_plugin_register(plugin);
5796 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5797 purple_account_user_split_set_reverse(split, FALSE);
5798 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5800 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5801 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5802 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5803 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5805 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5806 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5807 // Translators: noun (networking port)
5808 option = purple_account_option_int_new(_("Port"), "port", 5061);
5809 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5811 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5812 purple_account_option_add_list_item(option, _("Auto"), "auto");
5813 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5814 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5815 purple_account_option_add_list_item(option, _("UDP"), "udp");
5816 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5818 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5819 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5821 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5822 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5824 // TODO commented out so won't show in the preferences until we fix krb message signing
5826 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5827 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5829 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5830 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5831 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5834 my_protocol = plugin;
5837 /* I had to redefined the function for it load, but works */
5838 gboolean purple_init_plugin(PurplePlugin *plugin){
5839 plugin->info = &(info);
5840 init_plugin((plugin));
5841 sipe_plugin_load((plugin));
5842 return purple_plugin_register(plugin);
5846 Local Variables:
5847 mode: c
5848 c-file-style: "bsd"
5849 indent-tabs-mode: t
5850 tab-width: 8
5851 End: