Merge branch 'mob' of git+ssh://mob@repo.or.cz/srv/git/siplcs into mob
[siplcs.git] / src / sipe.c
blob544726ea28b1afd81bbf4a089403a48ef3a8ea89
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2007 Anibal Avelar <avelar@gmail.com>
8 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
10 * ***
11 * Thanks to Google's Summer of Code Program and the helpful mentors
12 * ***
14 * Session-based SIP MESSAGE documentation:
15 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 #ifndef _WIN32
33 #include <sys/socket.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
36 #include <netinet/in.h>
37 #include <net/if.h>
38 #ifdef ENABLE_NLS
39 # include <libintl.h>
40 # define _(String) ((const char *) gettext (String))
41 #else
42 # define _(String) ((const char *) (String))
43 #endif /* ENABLE_NLS */
44 #else
45 #ifdef _DLL
46 #define _WS2TCPIP_H_
47 #define _WINSOCK2API_
48 #define _LIBC_INTERNAL_
49 #endif /* _DLL */
51 #include "internal.h"
52 #endif /* _WIN32 */
54 #include <time.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <glib.h>
61 #include "accountopt.h"
62 #include "blist.h"
63 #include "conversation.h"
64 #include "dnsquery.h"
65 #include "debug.h"
66 #include "notify.h"
67 #include "privacy.h"
68 #include "prpl.h"
69 #include "plugin.h"
70 #include "util.h"
71 #include "version.h"
72 #include "network.h"
73 #include "xmlnode.h"
74 #include "mime.h"
76 #include "sipe.h"
77 #include "sip-ntlm.h"
78 #ifdef USE_KERBEROS
79 #include "sipkrb5.h"
80 #endif /*USE_KERBEROS*/
82 #include "sipmsg.h"
83 #include "sipe-sign.h"
84 #include "dnssrv.h"
85 #include "request.h"
87 /* Keep in sync with sipe_transport_type! */
88 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
89 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
91 static char *gentag()
93 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
96 static gchar *get_epid()
98 return sipe_uuid_get_macaddr();
101 static char *genbranch()
103 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
104 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
105 rand() & 0xFFFF, rand() & 0xFFFF);
108 static char *gencallid()
110 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
111 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
112 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
113 rand() & 0xFFFF, rand() & 0xFFFF);
116 static gchar *find_tag(const gchar *hdr)
118 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
119 if (!tag) {
120 // In case it's at the end and there's no trailing ;
121 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
123 return tag;
127 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
129 return "sipe";
132 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
134 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
135 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
136 gpointer data);
138 static void sipe_close(PurpleConnection *gc);
140 static void sipe_subscribe_to_name(struct sipe_account_data *sip, const char * buddy_name);
141 static void send_presence_info(struct sipe_account_data *sip);
143 static void sendout_pkt(PurpleConnection *gc, const char *buf);
145 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
147 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
148 if (timeout != NULL) {
149 sscanf(timeout, "%u", &sip->keepalive_timeout);
150 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
151 sip->keepalive_timeout);
155 static void sipe_keep_alive(PurpleConnection *gc)
157 struct sipe_account_data *sip = gc->proto_data;
158 if (sip->transport == SIPE_TRANSPORT_UDP) {
159 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
160 gchar buf[2] = {0, 0};
161 purple_debug_info("sipe", "sending keep alive\n");
162 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
163 } else {
164 time_t now = time(NULL);
165 if ((sip->keepalive_timeout > 0) &&
166 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
167 #if PURPLE_VERSION_CHECK(2,4,0)
168 && ((now - gc->last_received) >= sip->keepalive_timeout)
169 #endif
171 purple_debug_info("sipe", "sending keep alive\n");
172 sendout_pkt(gc, "\r\n\r\n");
173 sip->last_keepalive = now;
178 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
180 struct sip_connection *ret = NULL;
181 GSList *entry = sip->openconns;
182 while (entry) {
183 ret = entry->data;
184 if (ret->fd == fd) return ret;
185 entry = entry->next;
187 return NULL;
190 static void sipe_auth_free(struct sip_auth *auth)
192 g_free(auth->nonce);
193 auth->nonce = NULL;
194 g_free(auth->opaque);
195 auth->opaque = NULL;
196 g_free(auth->realm);
197 auth->realm = NULL;
198 g_free(auth->target);
199 auth->target = NULL;
200 g_free(auth->digest_session_key);
201 auth->digest_session_key = NULL;
202 g_free(auth->ntlm_key);
203 auth->ntlm_key = NULL;
204 auth->type = AUTH_TYPE_UNSET;
205 auth->retries = 0;
206 auth->expires = 0;
209 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
211 struct sip_connection *ret = g_new0(struct sip_connection, 1);
212 ret->fd = fd;
213 sip->openconns = g_slist_append(sip->openconns, ret);
214 return ret;
217 static void connection_remove(struct sipe_account_data *sip, int fd)
219 struct sip_connection *conn = connection_find(sip, fd);
220 if (conn) {
221 sip->openconns = g_slist_remove(sip->openconns, conn);
222 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
223 g_free(conn->inbuf);
224 g_free(conn);
228 static void connection_free_all(struct sipe_account_data *sip)
230 struct sip_connection *ret = NULL;
231 GSList *entry = sip->openconns;
232 while (entry) {
233 ret = entry->data;
234 connection_remove(sip, ret->fd);
235 entry = sip->openconns;
239 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
241 const gchar *method = msg->method;
242 const gchar *target = msg->target;
243 gchar noncecount[9];
244 gchar *response;
245 gchar *ret;
246 gchar *tmp;
247 const char *authdomain;
248 const char *authuser;
249 const char *krb5_realm;
250 const char *host;
251 gchar *krb5_token = NULL;
253 authdomain = purple_account_get_string(sip->account, "authdomain", "");
254 authuser = purple_account_get_string(sip->account, "authuser", sip->username);
256 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
257 // and do error checking
259 // KRB realm should always be uppercase
260 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
262 if (sip->realhostname) {
263 host = sip->realhostname;
264 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
265 host = purple_account_get_string(sip->account, "proxy", "");
266 } else {
267 host = sip->sipdomain;
270 /*gboolean new_auth = krb5_auth.gss_context == NULL;
271 if (new_auth) {
272 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
275 if (new_auth || force_reauth) {
276 krb5_token = krb5_auth.base64_token;
279 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
280 krb5_token = krb5_auth.base64_token;*/
282 if (!authuser || strlen(authuser) < 1) {
283 authuser = sip->username;
286 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
287 sprintf(noncecount, "%08d", auth->nc++);
288 response = purple_cipher_http_digest_calculate_response(
289 "md5", method, target, NULL, NULL,
290 auth->nonce, noncecount, NULL, auth->digest_session_key);
291 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
293 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);
294 g_free(response);
295 return ret;
296 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
297 // If we have a signature for the message, include that
298 if (msg->signature) {
299 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
300 return tmp;
303 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
304 const gchar * ntlm_key;
305 #if GLIB_CHECK_VERSION(2,8,0)
306 const gchar * hostname = g_get_host_name();
307 #else
308 static char hostname[256];
309 int ret = gethostname(hostname, sizeof(hostname));
310 hostname[sizeof(hostname) - 1] = '\0';
311 if (ret == -1 || hostname[0] == '\0') {
312 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name: %s. Using \"localhost.\"\n");
313 g_strerror(errno);
314 strcpy(hostname, "localhost");
316 #endif
317 /*const gchar * hostname = purple_get_host_name();*/
319 gchar * gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
320 auth->ntlm_key = (gchar *)ntlm_key;
321 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
322 g_free(gssapi_data);
323 return tmp;
326 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
327 return tmp;
328 } else if (auth->type == AUTH_TYPE_KERBEROS) {
329 /* Kerberos */
330 if (auth->nc == 3) {
331 /*if (new_auth || force_reauth) {
332 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
333 if (auth->opaque) {
334 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, krb5_token);
335 } else {
336 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
338 } else {
339 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
340 gchar * mic = "MICTODO";
341 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
342 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
343 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
344 //auth->opaque ? auth->opaque : "", auth->target);
345 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
346 //g_free(mic);
348 return tmp;
350 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
353 sprintf(noncecount, "%08d", auth->nc++);
354 response = purple_cipher_http_digest_calculate_response(
355 "md5", method, target, NULL, NULL,
356 auth->nonce, noncecount, NULL, auth->digest_session_key);
357 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
359 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);
360 g_free(response);
361 return ret;
364 static char *parse_attribute(const char *attrname, const char *source)
366 const char *tmp, *tmp2;
367 char *retval = NULL;
368 int len = strlen(attrname);
370 if (!strncmp(source, attrname, len)) {
371 tmp = source + len;
372 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
373 if (tmp2)
374 retval = g_strndup(tmp, tmp2 - tmp);
375 else
376 retval = g_strdup(tmp);
379 return retval;
382 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
384 int i = 0;
385 const char *authuser;
386 char *tmp;
387 gchar **parts;
388 const char *krb5_realm;
389 const char *host;
391 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
392 // and do error checking
394 // KRB realm should always be uppercase
395 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
397 if (sip->realhostname) {
398 host = sip->realhostname;
399 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
400 host = purple_account_get_string(sip->account, "proxy", "");
401 } else {
402 host = sip->sipdomain;
405 authuser = purple_account_get_string(sip->account, "authuser", sip->username);
407 if (!authuser || strlen(authuser) < 1) {
408 authuser = sip->username;
411 if (!hdr) {
412 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
413 return;
416 if (!g_strncasecmp(hdr, "NTLM", 4)) {
417 auth->type = AUTH_TYPE_NTLM;
418 parts = g_strsplit(hdr+5, "\", ", 0);
419 i = 0;
420 while (parts[i]) {
421 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
422 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
423 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
424 g_free(tmp);
426 if ((tmp = parse_attribute("targetname=\"",
427 parts[i]))) {
428 auth->target = tmp;
430 else if ((tmp = parse_attribute("realm=\"",
431 parts[i]))) {
432 auth->realm = tmp;
434 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
435 auth->opaque = tmp;
437 i++;
439 g_strfreev(parts);
440 auth->nc = 1;
441 if (!strstr(hdr, "gssapi-data")) {
442 auth->nc = 1;
443 } else {
444 auth->nc = 3;
446 return;
449 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
450 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
451 auth->type = AUTH_TYPE_KERBEROS;
452 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
453 parts = g_strsplit(hdr+9, "\", ", 0);
454 i = 0;
455 while (parts[i]) {
456 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
457 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
458 /*if (krb5_auth.gss_context == NULL) {
459 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
461 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
462 g_free(tmp);
464 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
465 auth->target = tmp;
466 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
467 auth->realm = tmp;
468 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
469 auth->opaque = tmp;
471 i++;
473 g_strfreev(parts);
474 auth->nc = 3;
475 return;
478 auth->type = AUTH_TYPE_DIGEST;
479 parts = g_strsplit(hdr, " ", 0);
480 while (parts[i]) {
481 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
482 auth->nonce = tmp;
484 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
485 auth->realm = tmp;
487 i++;
489 g_strfreev(parts);
491 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
492 if (auth->realm) {
493 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
494 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
496 auth->nc = 1;
500 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
502 PurpleConnection *gc = data;
503 struct sipe_account_data *sip = gc->proto_data;
504 gsize max_write;
505 gssize written;
507 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
509 if (max_write == 0) {
510 if (sip->tx_handler != 0){
511 purple_input_remove(sip->tx_handler);
512 sip->tx_handler = 0;
514 return;
517 written = write(sip->fd, sip->txbuf->outptr, max_write);
519 if (written < 0 && errno == EAGAIN)
520 written = 0;
521 else if (written <= 0) {
522 /*TODO: do we really want to disconnect on a failure to write?*/
523 purple_connection_error(gc, _("Could not write"));
524 return;
527 purple_circ_buffer_mark_read(sip->txbuf, written);
530 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
532 PurpleConnection *gc = data;
533 struct sipe_account_data *sip = gc->proto_data;
534 gsize max_write;
535 gssize written;
537 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
539 if (max_write == 0) {
540 if (sip->tx_handler != 0) {
541 purple_input_remove(sip->tx_handler);
542 sip->tx_handler = 0;
543 return;
547 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
549 if (written < 0 && errno == EAGAIN)
550 written = 0;
551 else if (written <= 0) {
552 /*TODO: do we really want to disconnect on a failure to write?*/
553 purple_connection_error(gc, _("Could not write"));
554 return;
557 purple_circ_buffer_mark_read(sip->txbuf, written);
560 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
562 static void send_later_cb(gpointer data, gint source, const gchar *error)
564 PurpleConnection *gc = data;
565 struct sipe_account_data *sip;
566 struct sip_connection *conn;
568 if (!PURPLE_CONNECTION_IS_VALID(gc))
570 if (source >= 0)
571 close(source);
572 return;
575 if (source < 0) {
576 purple_connection_error(gc, _("Could not connect"));
577 return;
580 sip = gc->proto_data;
581 sip->fd = source;
582 sip->connecting = FALSE;
583 sip->last_keepalive = time(NULL);
585 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
587 /* If there is more to write now, we need to register a handler */
588 if (sip->txbuf->bufused > 0)
589 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
591 conn = connection_create(sip, source);
592 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
595 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
597 struct sipe_account_data *sip;
598 struct sip_connection *conn;
600 if (!PURPLE_CONNECTION_IS_VALID(gc))
602 if (gsc) purple_ssl_close(gsc);
603 return NULL;
606 sip = gc->proto_data;
607 sip->fd = gsc->fd;
608 sip->gsc = gsc;
609 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
610 sip->connecting = FALSE;
611 sip->last_keepalive = time(NULL);
613 conn = connection_create(sip, gsc->fd);
615 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
617 return sip;
620 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
622 PurpleConnection *gc = data;
623 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
624 if (sip == NULL) return;
626 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
628 /* If there is more to write now */
629 if (sip->txbuf->bufused > 0) {
630 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
635 static void sendlater(PurpleConnection *gc, const char *buf)
637 struct sipe_account_data *sip = gc->proto_data;
639 if (!sip->connecting) {
640 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
641 if (sip->transport == SIPE_TRANSPORT_TLS){
642 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
643 } else {
644 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
645 purple_connection_error(gc, _("Couldn't create socket"));
648 sip->connecting = TRUE;
651 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
652 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
654 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
657 static void sendout_pkt(PurpleConnection *gc, const char *buf)
659 struct sipe_account_data *sip = gc->proto_data;
660 time_t currtime = time(NULL);
661 int writelen = strlen(buf);
663 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
664 if (sip->transport == SIPE_TRANSPORT_UDP) {
665 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
666 purple_debug_info("sipe", "could not send packet\n");
668 } else {
669 int ret;
670 if (sip->fd < 0) {
671 sendlater(gc, buf);
672 return;
675 if (sip->tx_handler) {
676 ret = -1;
677 errno = EAGAIN;
678 } else{
679 if (sip->gsc){
680 ret = purple_ssl_write(sip->gsc, buf, writelen);
681 }else{
682 ret = write(sip->fd, buf, writelen);
686 if (ret < 0 && errno == EAGAIN)
687 ret = 0;
688 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
689 sendlater(gc, buf);
690 return;
693 if (ret < writelen) {
694 if (!sip->tx_handler){
695 if (sip->gsc){
696 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
698 else{
699 sip->tx_handler = purple_input_add(sip->fd,
700 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
701 gc);
705 /* XXX: is it OK to do this? You might get part of a request sent
706 with part of another. */
707 if (sip->txbuf->bufused > 0)
708 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
710 purple_circ_buffer_append(sip->txbuf, buf + ret,
711 writelen - ret);
716 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
718 sendout_pkt(gc, buf);
719 return len;
722 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
724 GSList *tmp = msg->headers;
725 gchar *name;
726 gchar *value;
727 GString *outstr = g_string_new("");
728 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
729 while (tmp) {
730 name = ((struct siphdrelement*) (tmp->data))->name;
731 value = ((struct siphdrelement*) (tmp->data))->value;
732 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
733 tmp = g_slist_next(tmp);
735 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
736 sendout_pkt(sip->gc, outstr->str);
737 g_string_free(outstr, TRUE);
740 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
742 gchar * buf;
743 if (sip->registrar.ntlm_key) {
744 struct sipmsg_breakdown msgbd;
745 msgbd.msg = msg;
746 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
747 msgbd.rand = g_strdup_printf("%08x", g_random_int());
748 sip->registrar.ntlm_num++;
749 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
750 gchar * signature_input_str = sipmsg_breakdown_get_string(&msgbd);
751 if (signature_input_str != NULL) {
752 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
753 msg->rand = g_strdup(msgbd.rand);
754 msg->num = g_strdup(msgbd.num);
756 sipmsg_breakdown_free(&msgbd);
759 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
760 buf = auth_header(sip, &sip->registrar, msg);
761 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
762 sipmsg_add_header(msg, "Authorization", buf);
763 } else {
764 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
766 g_free(buf);
767 } 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")) {
768 sip->registrar.nc = 3;
769 sip->registrar.type = AUTH_TYPE_NTLM;
771 buf = auth_header(sip, &sip->registrar, msg);
772 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
773 g_free(buf);
774 } else {
775 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
779 static char *get_contact(struct sipe_account_data *sip)
781 return g_strdup(sip->contact);
785 static char *get_contact_service(struct sipe_account_data *sip)
787 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()));
788 //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);
791 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
792 const char *text, const char *body)
794 gchar *name;
795 gchar *value;
796 GString *outstr = g_string_new("");
797 struct sipe_account_data *sip = gc->proto_data;
799 sipmsg_remove_header(msg, "ms-user-data");
801 gchar *contact;
802 contact = get_contact(sip);
803 sipmsg_remove_header(msg, "Contact");
804 sipmsg_add_header(msg, "Contact", contact);
805 g_free(contact);
807 /* When sending the acknowlegements and errors, the content length from the original
808 message is still here, but there is no body; we need to make sure we're sending the
809 correct content length */
810 sipmsg_remove_header(msg, "Content-Length");
811 if (body) {
812 gchar len[12];
813 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
814 sipmsg_add_header(msg, "Content-Length", len);
815 } else {
816 sipmsg_add_header(msg, "Content-Length", "0");
819 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
820 //gchar * mic = "MICTODO";
821 msg->response = code;
823 sipmsg_remove_header(msg, "Authentication-Info");
824 sign_outgoing_message(msg, sip, msg->method);
826 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
827 GSList *tmp = msg->headers;
828 while (tmp) {
829 name = ((struct siphdrelement*) (tmp->data))->name;
830 value = ((struct siphdrelement*) (tmp->data))->value;
832 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
833 tmp = g_slist_next(tmp);
835 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
836 sendout_pkt(gc, outstr->str);
837 g_string_free(outstr, TRUE);
840 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
842 if (trans->msg) sipmsg_free(trans->msg);
843 sip->transactions = g_slist_remove(sip->transactions, trans);
844 g_free(trans);
847 static struct transaction *
848 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
850 struct transaction *trans = g_new0(struct transaction, 1);
851 trans->time = time(NULL);
852 trans->msg = (struct sipmsg *)msg;
853 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
854 trans->callback = callback;
855 sip->transactions = g_slist_append(sip->transactions, trans);
856 return trans;
859 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
861 struct transaction *trans;
862 GSList *transactions = sip->transactions;
863 gchar *cseq = sipmsg_find_header(msg, "CSeq");
865 while (transactions) {
866 trans = transactions->data;
867 if (!strcmp(trans->cseq, cseq)) {
868 return trans;
870 transactions = transactions->next;
873 return NULL;
876 static struct transaction *
877 send_sip_request(PurpleConnection *gc, const gchar *method,
878 const gchar *url, const gchar *to, const gchar *addheaders,
879 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
881 struct sipe_account_data *sip = gc->proto_data;
882 const char *addh = "";
883 char *buf;
884 struct sipmsg *msg;
885 gchar *ptmp;
886 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
887 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
888 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
889 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
890 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
891 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
892 gchar *route = strdup("");
894 if (dialog && dialog->routes)
896 GSList *iter = dialog->routes;
898 while(iter)
900 char *tmp = route;
901 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
902 g_free(tmp);
903 iter = g_slist_next(iter);
907 if (!ourtag && !dialog) {
908 ourtag = gentag();
911 if (!strcmp(method, "REGISTER")) {
912 if (sip->regcallid) {
913 g_free(callid);
914 callid = g_strdup(sip->regcallid);
915 } else {
916 sip->regcallid = g_strdup(callid);
920 if (addheaders) addh = addheaders;
922 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
923 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
924 "From: <sip:%s>%s%s;epid=%s\r\n"
925 "To: <%s>%s%s%s%s\r\n"
926 "Max-Forwards: 70\r\n"
927 "CSeq: %d %s\r\n"
928 "User-Agent: %s\r\n"
929 "Call-ID: %s\r\n"
930 "%s%s"
931 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
932 method,
933 dialog && dialog->request ? dialog->request : url,
934 TRANSPORT_DESCRIPTOR,
935 purple_network_get_my_ip(-1),
936 sip->listenport,
937 branch ? ";branch=" : "",
938 branch ? branch : "",
939 sip->username,
940 ourtag ? ";tag=" : "",
941 ourtag ? ourtag : "",
942 get_epid(), // TODO generate one per account/login
944 theirtag ? ";tag=" : "",
945 theirtag ? theirtag : "",
946 theirepid ? ";epid=" : "",
947 theirepid ? theirepid : "",
948 dialog ? ++dialog->cseq : ++sip->cseq,
949 method,
950 useragent,
951 callid,
952 route,
953 addh,
954 body ? strlen(body) : 0,
955 body ? body : "");
958 //printf ("parsing msg buf:\n%s\n\n", buf);
959 msg = sipmsg_parse_msg(buf);
961 g_free(buf);
962 g_free(ourtag);
963 g_free(theirtag);
964 g_free(theirepid);
965 g_free(branch);
966 g_free(callid);
967 g_free(route);
969 sign_outgoing_message (msg, sip, method);
971 buf = sipmsg_to_string (msg);
973 /* add to ongoing transactions */
974 struct transaction * trans = transactions_add_buf(sip, msg, tc);
975 sendout_pkt(gc, buf);
977 return trans;
980 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
982 gchar *from = g_strdup_printf("sip:%s", sip->username);
983 gchar *contact = get_contact(sip);
984 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
985 "Content-Type: application/SOAP+xml\r\n",contact);
987 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
988 tr->payload = payload;
990 g_free(from);
991 g_free(hdr);
994 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
996 send_soap_request_with_cb(sip, body, NULL, NULL);
999 static char *get_contact_register(struct sipe_account_data *sip)
1001 return g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, generateUUIDfromEPID(get_epid()));
1004 static void do_register_exp(struct sipe_account_data *sip, int expire)
1006 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1007 char *to = g_strdup_printf("sip:%s", sip->username);
1008 char *contact = get_contact_register(sip);
1009 char *hdr = g_strdup_printf("Contact: %s\r\n"
1010 "Supported: gruu-10, adhoclist\r\n"
1011 "Event: registration\r\n"
1012 "Allow-Events: presence\r\n"
1013 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1014 "Expires: %d\r\n", contact,expire);
1015 g_free(contact);
1017 sip->registerstatus = 1;
1019 if (expire) {
1020 sip->reregister = time(NULL) + expire - 50;
1021 } else {
1022 sip->reregister = time(NULL) + 600;
1025 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1026 process_register_response);
1028 g_free(hdr);
1029 g_free(uri);
1030 g_free(to);
1033 static void do_register(struct sipe_account_data *sip)
1035 do_register_exp(sip, sip->registerexpire);
1038 static gchar *parse_from(const gchar *hdr)
1040 gchar *from;
1041 const gchar *tmp, *tmp2 = hdr;
1043 if (!hdr) return NULL;
1044 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1045 tmp = strchr(hdr, '<');
1047 /* i hate the different SIP UA behaviours... */
1048 if (tmp) { /* sip address in <...> */
1049 tmp2 = tmp + 1;
1050 tmp = strchr(tmp2, '>');
1051 if (tmp) {
1052 from = g_strndup(tmp2, tmp - tmp2);
1053 } else {
1054 purple_debug_info("sipe", "found < without > in From\n");
1055 return NULL;
1057 } else {
1058 tmp = strchr(tmp2, ';');
1059 if (tmp) {
1060 from = g_strndup(tmp2, tmp - tmp2);
1061 } else {
1062 from = g_strdup(tmp2);
1065 purple_debug_info("sipe", "got %s\n", from);
1066 return from;
1069 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1071 va_list args;
1072 xmlnode * node;
1073 const gchar * name;
1075 va_start(args, parent);
1076 while ((name = va_arg(args, const char *)) != NULL) {
1077 node = xmlnode_get_child(parent, name);
1078 if (node == NULL) return NULL;
1079 parent = node;
1081 va_end(args);
1083 return node;
1087 static void
1088 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1090 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1091 send_soap_request(sip, body);
1092 g_free(body);
1095 static void
1096 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1098 if (allow) {
1099 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1100 } else {
1101 purple_debug_info("sipe", "Blocking contact %s\n", who);
1104 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1107 static
1108 void sipe_auth_user_cb(void * data)
1110 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1111 if (!job) return;
1113 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1114 g_free(job);
1117 static
1118 void sipe_deny_user_cb(void * data)
1120 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1121 if (!job) return;
1123 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1124 g_free(job);
1127 static void
1128 sipe_add_permit(PurpleConnection *gc, const char *name)
1130 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1131 sipe_contact_allow_deny(sip, name, TRUE);
1134 static void
1135 sipe_add_deny(PurpleConnection *gc, const char *name)
1137 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1138 sipe_contact_allow_deny(sip, name, FALSE);
1141 /*static void
1142 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1144 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1145 sipe_contact_set_acl(sip, name, "");
1148 static void
1149 sipe_process_incoming_pending (struct sipe_account_data *sip, struct sipmsg * msg)
1151 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1152 if (msg->response != 0 && msg->response != 200) return;
1154 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1156 xmlnode * watchers = xmlnode_from_str(msg->body, msg->bodylen);
1157 if (!watchers) return;
1159 xmlnode * watcher;
1160 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1161 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1162 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1163 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1165 // TODO pull out optional displayName to pass as alias
1166 if (remote_user) {
1167 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1168 job->who = remote_user;
1169 job->sip = sip;
1170 purple_account_request_authorization(
1171 sip->account,
1172 remote_user,
1173 NULL, // id
1174 alias,
1175 NULL, // message
1176 on_list,
1177 sipe_auth_user_cb,
1178 sipe_deny_user_cb,
1179 (void *) job);
1184 xmlnode_free(watchers);
1185 return;
1188 static void
1189 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1191 PurpleGroup * purple_group = purple_find_group(group->name);
1192 if (!purple_group) {
1193 purple_group = purple_group_new(group->name);
1194 purple_blist_add_group(purple_group, NULL);
1197 if (purple_group) {
1198 group->purple_group = purple_group;
1199 sip->groups = g_slist_append(sip->groups, group);
1200 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1201 } else {
1202 purple_debug_info("sipe", "did not add group %s\n", group->name);
1206 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1208 if (sip == NULL) {
1209 return NULL;
1212 struct sipe_group *group;
1213 GSList *entry = sip->groups;
1214 while (entry) {
1215 group = entry->data;
1216 if (group->id == id) {
1217 return group;
1219 entry = entry->next;
1221 return NULL;
1224 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1226 if (sip == NULL) {
1227 return NULL;
1230 struct sipe_group *group;
1231 GSList *entry = sip->groups;
1232 while (entry) {
1233 group = entry->data;
1234 if (!strcmp(group->name, name)) {
1235 return group;
1237 entry = entry->next;
1239 return NULL;
1242 static void
1243 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1245 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1246 gchar * body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1247 send_soap_request(sip, body);
1248 g_free(body);
1249 g_free(group->name);
1250 group->name = g_strdup(name);
1253 static void
1254 sipe_group_set_user (struct sipe_account_data *sip, struct sipe_group * group, const gchar * who)
1256 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1257 PurpleBuddy * purple_buddy = purple_find_buddy (sip->account, who);
1259 if (!group) {
1260 group = sipe_group_find_by_id (sip, buddy->group_id);
1262 buddy->group_id = group ? group->id : 1;
1264 if (buddy && purple_buddy) {
1265 gchar * alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1266 purple_debug_info("sipe", "Saving buddy %s with alias %s and group_id %d\n", who, alias, buddy->group_id);
1267 gchar * body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1268 alias, buddy->group_id, "true", buddy->name, sip->contacts_delta++
1270 send_soap_request(sip, body);
1271 g_free(body);
1275 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1277 if (msg->response == 200) {
1278 struct sipe_group * group = g_new0(struct sipe_group, 1);
1280 struct group_user_context * ctx = (struct group_user_context*)tc->payload;
1281 group->name = ctx->group_name;
1283 xmlnode * xml = xmlnode_from_str(msg->body, msg->bodylen);
1284 if (!xml) return FALSE;
1286 xmlnode * node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1287 if (!node) return FALSE;
1289 char * group_id = xmlnode_get_data(node);
1290 if (!group_id) return FALSE;
1292 group->id = (int)g_ascii_strtod(group_id, NULL);
1294 sipe_group_add(sip, group);
1295 sipe_group_set_user(sip, group, ctx->user_name);
1297 g_free(ctx);
1298 xmlnode_free(xml);
1299 return TRUE;
1301 return FALSE;
1304 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1306 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1307 ctx->group_name = g_strdup(name);
1308 ctx->user_name = g_strdup(who);
1310 gchar * body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1311 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1312 g_free(body);
1315 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1317 gchar *to;
1319 if (msg->response == 200 || msg->response == 202) {
1320 return TRUE;
1323 to = parse_from(sipmsg_find_header(tc->msg, "To")); /* cant be NULL since it is our own msg */
1325 /* we can not subscribe -> user is offline (TODO unknown status?) */
1327 purple_prpl_got_user_status(sip->account, to, "offline", NULL);
1328 g_free(to);
1329 return TRUE;
1332 static void sipe_subscribe_to_name(struct sipe_account_data *sip, const char * buddy_name)
1334 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1335 gchar *tmp = get_contact(sip);
1336 gchar *request;
1337 gchar *content;
1339 //Add the the extend SUBSCRIBE request
1340 if (sip->presence_method_version == 1)
1342 request = g_strdup_printf(
1343 "Require: adhoclist, categoryList\r\n"
1344 "Supported: eventlist\r\n"
1345 "Accept: application/msrtc-event-categories+xml, application/xpidf+xml, text/xml+msrtc.pidf, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1346 "Event: presence\r\n"
1347 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1348 "Contact: %s\r\n", tmp);
1350 else{ //To send a single SUSCRIBE request
1351 request = g_strdup_printf(
1352 "Accept: application/msrtc-event-categories+xml, application/xpidf+xml, text/xml+msrtc.pidf, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1353 "Event: presence\r\n"
1354 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1355 "Contact: %s\r\n", tmp);
1358 content = g_strdup_printf(
1359 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1360 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1361 "<resource uri=\"%s\"/>\n"
1362 "</adhocList>\n"
1363 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1364 "<category name=\"note\"/>\n"
1365 "<category name=\"state\"/>\n"
1366 "</categoryList>\n"
1367 "</action>\n"
1368 "</batchSub>", sip->username, to
1371 g_free(tmp);
1373 /* subscribe to buddy presence */
1374 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1376 g_free(content);
1377 g_free(to);
1378 g_free(request);
1381 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1383 const char *status_id = purple_status_get_id(status);
1384 struct sipe_account_data *sip = NULL;
1386 if (!purple_status_is_active(status))
1387 return;
1389 if (account->gc)
1390 sip = account->gc->proto_data;
1392 if (sip) {
1393 g_free(sip->status);
1394 sip->status = g_strdup(status_id);
1395 send_presence_info(sip);
1399 static void
1400 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1402 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1403 sipe_group_set_user(sip, NULL, name);
1406 static void
1407 sipe_group_buddy(PurpleConnection *gc,
1408 const char *who,
1409 const char *old_group_name,
1410 const char *new_group_name)
1412 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1413 struct sipe_group * group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1414 if (!group) {
1415 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1416 } else {
1417 sipe_group_set_user(sip, group, who);
1421 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1423 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1424 struct sipe_buddy *b;
1426 // Prepend sip: if needed
1427 if (strncmp("sip:", buddy->name, 4)) {
1428 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1429 purple_blist_rename_buddy(buddy, buf);
1430 g_free(buf);
1433 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1434 b = g_new0(struct sipe_buddy, 1);
1435 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1436 b->name = g_strdup(buddy->name);
1437 g_hash_table_insert(sip->buddies, b->name, b);
1438 sipe_group_buddy(gc, b->name, NULL, group->name);
1439 } else {
1440 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1444 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1446 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1447 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1449 if (!b) return;
1450 g_hash_table_remove(sip->buddies, buddy->name);
1452 if (b->name) {
1453 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1454 send_soap_request(sip, body);
1455 g_free(body);
1458 g_free(b->name);
1459 g_free(b);
1462 static void
1463 sipe_rename_group(PurpleConnection *gc,
1464 const char *old_name,
1465 PurpleGroup *group,
1466 GList *moved_buddies)
1468 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1469 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1470 if (group) {
1471 sipe_group_rename(sip, s_group, group->name);
1472 } else {
1473 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1477 static void
1478 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1480 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1481 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1482 if (s_group) {
1483 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1484 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1485 send_soap_request(sip, body);
1486 g_free(body);
1488 sip->groups = g_slist_remove(sip->groups, s_group);
1489 g_free(s_group->name);
1490 } else {
1491 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1495 static GList *sipe_status_types(PurpleAccount *acc)
1497 PurpleStatusType *type;
1498 GList *types = NULL;
1500 // Online
1501 type = purple_status_type_new_with_attrs(
1502 PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE,
1503 // Translators: noun
1504 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1505 NULL);
1506 types = g_list_append(types, type);
1508 // Busy
1509 type = purple_status_type_new_with_attrs(
1510 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1511 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1512 NULL);
1513 types = g_list_append(types, type);
1515 // Do Not Disturb (Not let user set it)
1516 type = purple_status_type_new_with_attrs(
1517 PURPLE_STATUS_UNAVAILABLE, "do-not-disturb", "Do Not Disturb", TRUE, FALSE, FALSE,
1518 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1519 NULL);
1520 types = g_list_append(types, type);
1522 // Be Right Back
1523 type = purple_status_type_new_with_attrs(
1524 PURPLE_STATUS_AWAY, "be-right-back", _("Be Right Back"), TRUE, TRUE, FALSE,
1525 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1526 NULL);
1527 types = g_list_append(types, type);
1529 // Away
1530 type = purple_status_type_new_with_attrs(
1531 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1532 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1533 NULL);
1534 types = g_list_append(types, type);
1536 //On The Phone
1537 type = purple_status_type_new_with_attrs(
1538 PURPLE_STATUS_UNAVAILABLE, "on-the-phone", _("On The Phone"), TRUE, TRUE, FALSE,
1539 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1540 NULL);
1541 types = g_list_append(types, type);
1543 //Out To Lunch
1544 type = purple_status_type_new_with_attrs(
1545 PURPLE_STATUS_AWAY, "out-to-lunch", "Out To Lunch", TRUE, TRUE, FALSE,
1546 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1547 NULL);
1548 types = g_list_append(types, type);
1550 //Appear Offline
1551 type = purple_status_type_new_full(
1552 PURPLE_STATUS_INVISIBLE, NULL, "Appear Offline", TRUE, TRUE, FALSE);
1553 types = g_list_append(types, type);
1555 // Offline
1556 type = purple_status_type_new_full(
1557 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1558 types = g_list_append(types, type);
1560 return types;
1563 static gboolean sipe_add_lcs_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1565 int len = msg->bodylen;
1567 gchar *tmp = sipmsg_find_header(msg, "Event");
1568 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1569 return FALSE;
1572 /* Convert the contact from XML to Purple Buddies */
1573 xmlnode * isc = xmlnode_from_str(msg->body, len);
1574 if (!isc) {
1575 return FALSE;
1578 gchar * contacts_delta = g_strdup(xmlnode_get_attrib(isc, "deltaNum"));
1579 if (contacts_delta) {
1580 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1583 /* Parse groups */
1584 xmlnode *group_node;
1585 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1586 struct sipe_group * group = g_new0(struct sipe_group, 1);
1588 group->name = g_strdup(xmlnode_get_attrib(group_node, "name"));
1589 if (!strncmp(group->name, "~", 1)){
1590 // TODO translate
1591 group->name = "General";
1593 group->name = g_strdup(group->name);
1594 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1596 sipe_group_add(sip, group);
1599 // Make sure we have at least one group
1600 if (g_slist_length(sip->groups) == 0) {
1601 struct sipe_group * group = g_new0(struct sipe_group, 1);
1602 // TODO translate
1603 group->name = g_strdup("General");
1604 group->id = 1;
1605 PurpleGroup * purple_group = purple_group_new(group->name);
1606 purple_blist_add_group(purple_group, NULL);
1607 sip->groups = g_slist_append(sip->groups, group);
1610 /* Parse contacts */
1611 xmlnode *item;
1612 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1613 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1614 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1615 gchar **item_groups = g_strsplit(xmlnode_get_attrib(item, "groups"), " ", 0);
1617 struct sipe_group * group = NULL;
1619 // Find the first group this contact belongs to; that's where we'll place it in the buddy list
1620 if (item_groups[0]) {
1621 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[0], NULL));
1624 // If couldn't find the right group for this contact, just put them in the first group we have
1625 if (group == NULL && g_slist_length(sip->groups) > 0) {
1626 group = sip->groups->data;
1629 if (group != NULL) {
1630 char * buddy_name = g_strdup_printf("sip:%s", uri);
1632 //b = purple_find_buddy(sip->account, buddy_name);
1633 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1634 if (!b){
1635 b = purple_buddy_new(sip->account, buddy_name, uri);
1637 g_free(buddy_name);
1639 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1641 if (name != NULL && strlen(name) != 0) {
1642 purple_blist_alias_buddy(b, name);
1643 } else {
1644 purple_blist_alias_buddy(b, uri);
1647 struct sipe_buddy * buddy = g_new0(struct sipe_buddy, 1);
1648 buddy->name = g_strdup(b->name);
1649 buddy->group_id = group->id;
1650 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1652 purple_debug_info("sipe", "Added buddy %s to group %s\n", buddy->name, group->name);
1653 } else {
1654 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1655 name);
1659 xmlnode_free(isc);
1661 return 0;
1664 static void sipe_subscribe_buddylist(struct sipe_account_data *sip,struct sipmsg *msg)
1666 gchar *to = g_strdup_printf("sip:%s", sip->username);
1667 gchar *tmp = get_contact(sip);
1668 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-roaming-contacts\r\n"
1669 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
1670 "Supported: com.microsoft.autoextend\r\n"
1671 "Supported: ms-benotify\r\n"
1672 "Proxy-Require: ms-benotify\r\n"
1673 "Supported: ms-piggyback-first-notify\r\n"
1674 "Contact: %s\r\n", tmp);
1675 g_free(tmp);
1677 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_add_lcs_contacts);
1678 g_free(to);
1679 g_free(hdr);
1682 static gboolean
1683 sipe_process_pending_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1685 sipe_process_incoming_pending (sip, msg);
1686 return TRUE;
1689 static void sipe_subscribe_pending_buddies(struct sipe_account_data *sip,struct sipmsg *msg)
1691 gchar *to = g_strdup_printf("sip:%s", sip->username);
1692 gchar *tmp = get_contact(sip);
1693 gchar *hdr = g_strdup_printf("Event: presence.wpending\r\n"
1694 "Accept: text/xml+msrtc.wpending\r\n"
1695 "Supported: com.microsoft.autoextend\r\n"
1696 "Supported: ms-benotify\r\n"
1697 "Proxy-Require: ms-benotify\r\n"
1698 "Supported: ms-piggyback-first-notify\r\n"
1699 "Contact: %s\r\n", tmp);
1700 g_free(tmp);
1702 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_pending_response);
1703 g_free(to);
1704 g_free(hdr);
1707 static void process_incoming_benotify(struct sipe_account_data *sip, struct sipmsg *msg)
1709 gchar * event = sipmsg_find_header(msg, "Event");
1710 if (!event) return;
1712 if (!strcmp(event, "presence.wpending")) {
1713 sipe_process_incoming_pending (sip, msg);
1714 return;
1717 xmlnode *xml = xmlnode_from_str(msg->body, msg->bodylen);
1718 if (!xml) return;
1720 gchar * contacts_delta = g_strdup(xmlnode_get_attrib(xml, "deltaNum"));
1721 if (contacts_delta) {
1722 int new_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1723 if (!strcmp(event, "vnd-microsoft-roaming-ACL")) {
1724 sip->acl_delta = new_delta;
1725 } else {
1726 sip->contacts_delta = new_delta;
1730 xmlnode_free(xml);
1733 static gboolean
1734 sipe_process_acl_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1736 process_incoming_benotify (sip, msg);
1737 return TRUE;
1740 static void sipe_subscribe_acl(struct sipe_account_data *sip,struct sipmsg *msg)
1742 gchar *to = g_strdup_printf("sip:%s", sip->username);
1743 gchar *tmp = get_contact(sip);
1744 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-roaming-ACL\r\n"
1745 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
1746 "Supported: com.microsoft.autoextend\r\n"
1747 "Supported: ms-benotify\r\n"
1748 "Proxy-Require: ms-benotify\r\n"
1749 "Supported: ms-piggyback-first-notify\r\n"
1750 "Contact: %s\r\n", tmp);
1751 g_free(tmp);
1753 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_process_acl_response);
1754 g_free(to);
1755 g_free(hdr);
1758 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
1760 gchar *to = g_strdup_printf("sip:%s", sip->username);
1761 gchar *tmp = get_contact(sip);
1762 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-roaming-self\r\n"
1763 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
1764 "Supported: com.microsoft.autoextend\r\n"
1765 "Supported: ms-benotify\r\n"
1766 "Proxy-Require: ms-benotify\r\n"
1767 "Supported: ms-piggyback-first-notify\r\n"
1768 "Contact: %s\r\n"
1769 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
1771 g_free(tmp);
1773 gchar *body=g_strdup("<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\"><roaming type=\"categories\"/><roaming type=\"containers\"/><roaming type=\"subscribers\"/></roamingList>");
1775 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
1776 g_free(body);
1777 g_free(to);
1778 g_free(hdr);
1781 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
1783 gchar *to = g_strdup_printf("sip:%s", sip->username);
1784 gchar *tmp = get_contact(sip);
1785 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-provisioning-v2\r\n"
1786 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
1787 "Supported: com.microsoft.autoextend\r\n"
1788 "Supported: ms-benotify\r\n"
1789 "Proxy-Require: ms-benotify\r\n"
1790 "Supported: ms-piggyback-first-notify\r\n"
1791 "Expires: 0\r\n"
1792 "Contact: %s\r\n"
1793 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
1795 g_free(tmp);
1797 gchar *body=g_strdup("<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\"><provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/><provisioningGroup name=\"ucPolicy\"/></provisioningGroupList>");
1798 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
1799 g_free(body);
1800 g_free(to);
1801 g_free(hdr);
1804 /* IM Session (INVITE and MESSAGE methods) */
1806 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
1808 if (sip == NULL || who == NULL) {
1809 return NULL;
1812 struct sip_im_session *session;
1813 GSList *entry = sip->im_sessions;
1814 while (entry) {
1815 session = entry->data;
1816 if ((who != NULL && !strcmp(who, session->with))) {
1817 return session;
1819 entry = entry->next;
1821 return NULL;
1824 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
1826 struct sip_im_session *session = find_im_session(sip, who);
1827 if (!session) {
1828 session = g_new0(struct sip_im_session, 1);
1829 session->with = g_strdup(who);
1830 sip->im_sessions = g_slist_append(sip->im_sessions, session);
1832 return session;
1835 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
1837 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
1838 // TODO free session resources
1841 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
1843 char *msg, *msg_tmp;
1844 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
1845 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
1846 g_free(msg_tmp);
1847 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
1848 "possibly because one or more persons are offline:\n%s") ,
1849 msg ? msg : "");
1850 purple_conv_present_error(with, sip->account, msg_tmp);
1851 g_free(msg);
1852 g_free(msg_tmp);
1855 static void sipe_im_remove_first_from_queue (struct sip_im_session * session);
1856 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
1858 static gboolean
1859 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1861 gboolean ret = TRUE;
1862 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
1863 struct sip_im_session * session = find_im_session(sip, with);
1865 if (!session) {
1866 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
1867 ret = FALSE;
1870 if (msg->response != 200) {
1871 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
1873 char *queued_msg;
1874 if (session->outgoing_message_queue) {
1875 queued_msg = session->outgoing_message_queue->data;
1877 sipe_present_message_undelivered_err(with, sip, queued_msg);
1878 im_session_destroy(sip, session);
1879 ret = FALSE;
1882 struct sip_dialog * dialog = session->dialog;
1883 if (!dialog) {
1884 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
1885 ret = FALSE;
1888 sipe_im_remove_first_from_queue(session);
1889 sipe_im_process_queue(sip, session);
1890 g_free(with);
1891 return ret;
1894 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
1896 gchar *hdr;
1897 gchar *fullto;
1898 gchar *tmp;
1900 if (strncmp("sip:", session->with, 4)) {
1901 fullto = g_strdup_printf("sip:%s", session->with);
1902 } else {
1903 fullto = g_strdup(session->with);
1906 char *msgformat;
1907 char *msgtext;
1908 sipe_parse_html(msg, &msgformat, &msgtext);
1909 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
1911 gchar *msgr_value = sipmsg_get_msgr_string(msgformat);
1912 g_free(msgformat);
1913 gchar *msgr = "";
1914 if (msgr_value) {
1915 msgr = g_strdup_printf(";msgr=%s", msgr_value);
1916 g_free(msgr_value);
1919 hdr = g_strdup_printf("Content-Type: text/plain; charset=UTF-8%s\r\n", msgr);
1920 g_free(msgr);
1921 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
1922 //hdr = g_strdup("Content-Type: text/rtf\r\n");
1923 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
1925 tmp = get_contact(sip);
1926 hdr = g_strdup_printf("Contact: %s\r\n%s", tmp, hdr);
1927 g_free(tmp);
1929 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
1930 g_free(msgtext);
1932 g_free(hdr);
1933 g_free(fullto);
1937 static void
1938 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
1940 GSList *entry = session->outgoing_message_queue;
1941 if (entry) {
1942 char *queued_msg = entry->data;
1943 sipe_send_message(sip, session, queued_msg);
1947 static void
1948 sipe_im_remove_first_from_queue (struct sip_im_session * session)
1950 if (session && session->outgoing_message_queue) {
1951 char *queued_msg = session->outgoing_message_queue->data;
1952 // Remove from the queue and free the string
1953 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
1954 g_free(queued_msg);
1958 static void
1959 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
1961 GSList *hdr = msg->headers;
1962 struct siphdrelement *elem;
1963 gchar *contact;
1965 while(hdr)
1967 elem = hdr->data;
1968 if(!strcmp(elem->name, "Record-Route"))
1970 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
1971 dialog->routes = g_slist_append(dialog->routes, route);
1973 hdr = g_slist_next(hdr);
1976 if (outgoing)
1978 dialog->routes = g_slist_reverse(dialog->routes);
1981 if (dialog->routes)
1983 dialog->request = dialog->routes->data;
1984 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
1987 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
1988 dialog->routes = g_slist_append(dialog->routes, contact);
1991 static void
1992 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
1994 GSList *hdr = msg->headers;
1995 struct siphdrelement *elem;
1996 while(hdr)
1998 elem = hdr->data;
1999 if(!strcmp(elem->name, "Supported")
2000 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)strcmp))
2002 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2005 hdr = g_slist_next(hdr);
2009 static void
2010 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2012 gchar *us = outgoing ? "From" : "To";
2013 gchar *them = outgoing ? "To" : "From";
2015 dialog->callid = sipmsg_find_header(msg, "Call-ID");
2016 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2017 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2018 if (!dialog->theirepid) {
2019 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2021 if (!dialog->theirepid) {
2022 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2025 sipe_get_route_header(msg, dialog, outgoing);
2026 sipe_get_supported_header(msg, dialog, outgoing);
2030 static gboolean
2031 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2033 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2034 struct sip_im_session * session = find_im_session(sip, with);
2036 if (!session) {
2037 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2038 g_free(with);
2039 return FALSE;
2042 if (msg->response != 200) {
2043 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2045 char *queued_msg;
2046 if (session->outgoing_message_queue) {
2047 queued_msg = session->outgoing_message_queue->data;
2049 sipe_present_message_undelivered_err(with, sip, queued_msg);
2051 im_session_destroy(sip, session);
2052 g_free(with);
2053 return FALSE;
2056 struct sip_dialog * dialog = session->dialog;
2057 if (!dialog) {
2058 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2059 g_free(with);
2060 return FALSE;
2063 sipe_parse_dialog(msg, dialog, TRUE);
2064 dialog->cseq = 0;
2066 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2067 session->outgoing_invite = NULL;
2068 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)strcmp)) {
2069 sipe_im_remove_first_from_queue(session);
2070 } else {
2071 sipe_im_process_queue(sip, session);
2074 g_free(with);
2075 return TRUE;
2079 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
2081 gchar *hdr;
2082 gchar *to;
2083 gchar *contact;
2084 gchar *body;
2086 if (session->dialog) {
2087 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2088 return;
2091 session->dialog = g_new0(struct sip_dialog, 1);
2093 if (strstr(session->with, "sip:")) {
2094 to = g_strdup(session->with);
2095 } else {
2096 to = g_strdup_printf("sip:%s", session->with);
2099 char *msgformat;
2100 char *msgtext;
2101 sipe_parse_html(msg_body, &msgformat, &msgtext);
2102 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2104 gchar *msgr_value = sipmsg_get_msgr_string(msgformat);
2105 g_free(msgformat);
2106 gchar *msgr = "";
2107 if (msgr_value) {
2108 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2109 g_free(msgr_value);
2112 char * base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2113 g_free(msgtext);
2114 char * ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2115 g_free(msgr);
2116 g_free(base64_msg);
2118 contact = get_contact(sip);
2119 hdr = g_strdup_printf(
2120 "Contact: %s\r\n%s"
2121 "Content-Type: application/sdp\r\n",
2122 contact, ms_text_format, sip->username, sip->username, to);
2123 g_free(ms_text_format);
2125 body = g_strdup_printf(
2126 "v=0\r\n"
2127 "o=- 0 0 IN IP4 %s\r\n"
2128 "s=session\r\n"
2129 "c=IN IP4 %s\r\n"
2130 "t=0 0\r\n"
2131 "m=message %d sip null\r\n"
2132 "a=accept-types:text/plain text/html image/gif "
2133 "multipart/alternative application/im-iscomposing+xml\r\n",
2134 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2136 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2137 to, to, hdr, body, session->dialog, process_invite_response);
2139 g_free(to);
2140 g_free(body);
2141 g_free(hdr);
2142 g_free(contact);
2145 static void
2146 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2148 if (session) {
2149 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2150 im_session_destroy(sip, session);
2154 static void
2155 sipe_convo_closed(PurpleConnection * gc, const char *who)
2157 struct sipe_account_data *sip = gc->proto_data;
2159 purple_debug_info("sipe", "conversation with %s closed\n", who);
2160 im_session_close(sip, find_im_session(sip, who));
2163 static void
2164 im_session_close_all (struct sipe_account_data *sip)
2166 GSList *entry = sip->im_sessions;
2167 while (entry) {
2168 im_session_close (sip, entry->data);
2169 entry = sip->im_sessions;
2173 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2175 purple_debug_info("sipe", "sipe_im_send what=%s\n", what);
2177 struct sipe_account_data *sip = gc->proto_data;
2178 char *to = g_strdup(who);
2179 char *text = g_strdup(what);
2181 struct sip_im_session * session = find_or_create_im_session(sip, who);
2183 // Queue the message
2184 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
2186 if (session->dialog && session->dialog->callid) {
2187 sipe_im_process_queue(sip, session);
2188 } else if (!session->outgoing_invite) {
2189 // Need to send the INVITE to get the outgoing dialog setup
2190 sipe_invite(sip, session, text);
2193 g_free(to);
2194 return 1;
2198 /* End IM Session (INVITE and MESSAGE methods) */
2200 static unsigned int
2201 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2203 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2205 if (state == PURPLE_NOT_TYPING)
2206 return 0;
2208 struct sip_im_session * session = find_im_session(sip, who);
2210 if (session && session->dialog) {
2211 send_sip_request(gc, "INFO", who, who,
2212 "Content-Type: application/xml\r\n",
2213 SIPE_SEND_TYPING, session->dialog, NULL);
2216 return SIPE_TYPING_SEND_TIMEOUT;
2220 static void sipe_buddy_resub(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
2222 time_t curtime = time(NULL);
2223 if (buddy->resubscribe < curtime) {
2224 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_buddy_resub %s\n", name);
2225 sipe_subscribe_to_name(sip, buddy->name);
2227 /* resubscribe before subscription expires */
2228 /* add some jitter */
2229 buddy->resubscribe = time(NULL)+1140+(rand()%50);
2233 static gboolean resend_timeout(struct sipe_account_data *sip)
2235 GSList *tmp = sip->transactions;
2236 time_t currtime = time(NULL);
2237 while (tmp) {
2238 struct transaction *trans = tmp->data;
2239 tmp = tmp->next;
2240 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2241 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2242 /* TODO 408 */
2243 } else {
2244 if ((currtime - trans->time > 2) && trans->retries == 0) {
2245 trans->retries++;
2246 sendout_sipmsg(sip, trans->msg);
2250 return TRUE;
2253 static gboolean subscribe_timeout(struct sipe_account_data *sip)
2255 GSList *tmp;
2256 time_t curtime = time(NULL);
2257 /* register again if first registration or security token expires */
2258 if ( (sip->reregister < curtime)
2259 || (sip->registrar.expires != 0 && sip->registrar.expires < curtime) )
2261 /* time to do a full reauthentication? */
2262 if (sip->registrar.expires < curtime)
2264 /* we have to start a new authentication as the security token
2265 * is almost expired by sending a not signed REGISTER message */
2266 purple_debug_info("sipe", "do a full reauthentication");
2267 sipe_auth_free(&sip->registrar);
2268 sip->registerstatus = 0;
2270 do_register(sip);
2273 /* check for every subscription if we need to resubscribe */
2274 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_resub, (gpointer)sip);
2276 return TRUE;
2279 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2281 gchar *from;
2282 gchar *contenttype;
2283 gboolean found = FALSE;
2285 from = parse_from(sipmsg_find_header(msg, "From"));
2287 if (!from) return;
2289 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2291 contenttype = sipmsg_find_header(msg, "Content-Type");
2292 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2293 gchar *msgr = sipmsg_find_part_of_header(contenttype, "msgr=", NULL, NULL);
2294 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2295 g_free(msgr);
2297 gchar *body_esc = g_markup_escape_text(msg->body, -1);
2298 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2299 g_free(body_esc);
2300 g_free(x_mms_im_format);
2302 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2303 g_free(body_html);
2304 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2305 found = TRUE;
2307 if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2308 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2309 xmlnode *state;
2310 gchar *statedata;
2312 if (!isc) {
2313 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2314 return;
2317 state = xmlnode_get_child(isc, "state");
2319 if (!state) {
2320 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2321 xmlnode_free(isc);
2322 return;
2325 statedata = xmlnode_get_data(state);
2326 if (statedata) {
2327 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2328 else serv_got_typing_stopped(sip->gc, from);
2330 g_free(statedata);
2332 xmlnode_free(isc);
2333 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2334 found = TRUE;
2336 if (!found) {
2337 purple_debug_info("sipe", "got unknown mime-type");
2338 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2340 g_free(from);
2343 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2345 // Only accept text invitations
2346 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
2347 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2348 return;
2351 gchar * from = parse_from(sipmsg_find_header(msg, "From"));
2352 struct sip_im_session * session = find_or_create_im_session (sip, from);
2353 if (session) {
2354 if (session->dialog) {
2355 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2356 } else {
2357 session->dialog = g_new0(struct sip_dialog, 1);
2359 sipe_parse_dialog(msg, session->dialog, FALSE);
2361 session->dialog->callid = sipmsg_find_header(msg, "Call-ID");
2362 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2363 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2364 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2366 } else {
2367 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2370 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2371 gchar *ms_text_format = sipmsg_find_header(msg, "ms-text-format");
2372 if (ms_text_format && !strncmp(ms_text_format, "text/plain", 10)) {
2373 gchar *msgr = sipmsg_find_part_of_header(ms_text_format, "msgr=", ";", NULL);
2374 gchar *x_mms_im_format = sipmsg_get_x_mms_im_format(msgr);
2375 g_free(msgr);
2377 gchar *ms_body = sipmsg_find_part_of_header(ms_text_format, "ms-body=", NULL, NULL);
2378 if (ms_body) {
2379 gchar *body = purple_base64_decode(ms_body, NULL);
2380 g_free(ms_body);
2381 gchar *body_esc = g_markup_escape_text(body, -1);
2382 gchar *body_html = sipmsg_apply_x_mms_im_format(x_mms_im_format, body_esc);
2383 g_free(body_esc);
2384 g_free(body);
2385 serv_got_im(sip->gc, from, body_html, 0, time(NULL));
2386 g_free(body_html);
2387 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message reciept
2389 g_free(x_mms_im_format);
2391 g_free(from);
2393 sipmsg_remove_header(msg, "Ms-Conversation-ID");
2394 sipmsg_remove_header(msg, "Ms-Text-Format");
2395 sipmsg_remove_header(msg, "EndPoints");
2396 sipmsg_remove_header(msg, "User-Agent");
2397 sipmsg_remove_header(msg, "Roster-Manager");
2399 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
2400 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2402 send_sip_response(sip->gc, msg, 200, "OK", g_strdup_printf(
2403 "v=0\r\n"
2404 "o=- 0 0 IN IP4 %s\r\n"
2405 "s=session\r\n"
2406 "c=IN IP4 %s\r\n"
2407 "t=0 0\r\n"
2408 "m=message %d sip sip:%s\r\n"
2409 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2410 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2411 sip->realport, sip->username));
2414 static void sipe_connection_cleanup(struct sipe_account_data *);
2415 static void create_connection(struct sipe_account_data *, gchar *, int);
2417 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2419 gchar *tmp, krb5_token;
2420 const gchar *expires_header;
2421 int expires;
2423 expires_header = sipmsg_find_header(msg, "Expires");
2424 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2425 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2427 switch (msg->response) {
2428 case 200:
2429 if (expires == 0) {
2430 sip->registerstatus = 0;
2431 } else {
2432 sip->reregister += expires - sip->registerexpire; //adjust to allowed expire
2433 sip->registerexpire = expires;
2434 sip->registerstatus = 3;
2435 if (sip->registrar.expires == 0)
2437 /* we have to reauthenticate as our security token expires
2438 after eight hours (be five minutes early) */
2439 sip->registrar.expires = time(NULL) + (8 * 3600) - 360;
2441 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
2443 int i = 0;
2444 gchar *contact_hdr = NULL;
2445 gchar *gruu;
2446 gchar * uuid = generateUUIDfromEPID(get_epid());
2447 // There can be multiple Contact headers (one per location where the user is logged in) so
2448 // make sure to only get the one for this uuid
2449 for (i = 0; contact_hdr = sipmsg_find_header_instance (msg, "Contact", i); i++) {
2450 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
2451 if (valid_contact) {
2452 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
2453 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
2454 g_free(valid_contact);
2455 break;
2456 } else {
2457 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
2461 if(gruu) {
2462 sip->contact = g_strdup_printf("<%s>", gruu);
2463 g_free(gruu);
2464 } else {
2465 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
2466 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);
2469 /* get buddies from blist; Has a bug */
2470 subscribe_timeout(sip);
2472 tmp = sipmsg_find_header(msg, "Allow-Events");
2473 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
2474 sipe_subscribe_buddylist(sip, msg);
2477 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
2478 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
2479 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
2480 } else {
2481 tmp = sipmsg_find_header(msg, "ms-keep-alive");
2482 if (tmp) {
2483 sipe_keep_alive_timeout(sip, tmp);
2487 sipe_subscribe_acl(sip, msg);
2488 sipe_subscribe_roaming_self(sip, msg);
2489 sipe_subscribe_roaming_provisioning(sip, msg);
2490 sipe_subscribe_pending_buddies(sip, msg);
2491 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
2493 // Should we remove the transaction here?
2494 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
2495 transactions_remove(sip, tc);
2497 break;
2498 case 301:
2500 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
2502 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
2503 gchar **parts = g_strsplit(redirect + 4, ";", 0);
2504 gchar **tmp;
2505 gchar *hostname;
2506 int port = 0;
2507 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
2508 int i = 1;
2510 tmp = g_strsplit(parts[0], ":", 0);
2511 hostname = g_strdup(tmp[0]);
2512 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
2513 g_strfreev(tmp);
2515 while (parts[i]) {
2516 tmp = g_strsplit(parts[i], "=", 0);
2517 if (tmp[1]) {
2518 if (g_strcasecmp("transport", tmp[0]) == 0) {
2519 if (g_strcasecmp("tcp", tmp[1]) == 0) {
2520 transport = SIPE_TRANSPORT_TCP;
2521 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
2522 transport = SIPE_TRANSPORT_UDP;
2526 g_strfreev(tmp);
2527 i++;
2529 g_strfreev(parts);
2531 /* Close old connection */
2532 sipe_connection_cleanup(sip);
2534 /* Create new connection */
2535 sip->transport = transport;
2536 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
2537 hostname, port, TRANSPORT_DESCRIPTOR);
2538 create_connection(sip, hostname, port);
2541 break;
2542 case 401:
2543 if (sip->registerstatus != 2) {
2544 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
2545 if (sip->registrar.retries > 3) {
2546 sip->gc->wants_to_die = TRUE;
2547 purple_connection_error(sip->gc, _("Wrong Password"));
2548 return TRUE;
2550 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
2551 tmp = sipmsg_find_auth_header(msg, "NTLM");
2552 } else {
2553 tmp = sipmsg_find_auth_header(msg, "Kerberos");
2555 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
2556 fill_auth(sip, tmp, &sip->registrar);
2557 sip->registerstatus = 2;
2558 if (sip->account->disconnecting) {
2559 do_register_exp(sip, 0);
2560 } else {
2561 do_register(sip);
2564 break;
2565 case 403:
2567 const gchar *warning = sipmsg_find_header(msg, "Warning");
2568 if (warning != NULL) {
2569 /* Example header:
2570 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
2572 gchar **tmp = g_strsplit(warning, "\"", 0);
2573 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
2574 g_strfreev(tmp);
2575 } else {
2576 warning = _("You have been rejected by the server");
2579 sip->gc->wants_to_die = TRUE;
2580 purple_connection_error(sip->gc, warning);
2581 return TRUE;
2583 break;
2584 case 404:
2586 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2587 if (warning != NULL) {
2588 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2589 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
2590 g_free(reason);
2591 } else {
2592 warning = _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
2595 sip->gc->wants_to_die = TRUE;
2596 purple_connection_error(sip->gc, warning);
2597 return TRUE;
2599 break;
2600 case 503:
2602 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2603 if (warning != NULL) {
2604 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2605 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
2606 g_free(reason);
2607 } else {
2608 warning = _("Service unavailable: no reason given");
2611 sip->gc->wants_to_die = TRUE;
2612 purple_connection_error(sip->gc, warning);
2613 return TRUE;
2615 break;
2617 return TRUE;
2620 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
2622 const char *uri,*state;
2623 xmlnode *xn_list;
2624 xmlnode *xn_resource;
2625 xmlnode *xn_instance;
2627 xn_list = xmlnode_from_str(data, len);
2628 xn_resource = xmlnode_get_child(xn_list, "resource");
2629 if (!xn_resource) return;
2630 xn_instance = xmlnode_get_child(xn_resource, "instance");
2631 if (!xn_instance) return;
2632 state = xmlnode_get_attrib(xn_instance, "state");
2633 uri = xmlnode_get_attrib(xn_instance, "cid");
2634 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
2635 sip->presence_method_version= 0;
2636 sipe_subscribe_to_name(sip, uri);
2640 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
2642 const char *uri;
2643 xmlnode *xn_categories;
2644 xmlnode *xn_category;
2645 xmlnode *xn_node;
2646 int changed = 0;
2647 const char *activity = NULL;
2649 xn_categories = xmlnode_from_str(data, len);
2650 uri = xmlnode_get_attrib(xn_categories, "uri");
2652 purple_debug_info("sipe", "process_incoming_notify_rlmi\n");
2654 for (xn_category = xmlnode_get_child(xn_categories, "category");
2655 xn_category ;
2656 xn_category = xmlnode_get_next_twin(xn_category) )
2658 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
2660 if (!strcmp(attrVar, "note"))
2662 xn_node = xmlnode_get_child(xn_category, "note");
2663 if (!xn_node) continue;
2664 xn_node = xmlnode_get_child(xn_node, "body");
2665 if (!xn_node) continue;
2667 char *note = xmlnode_get_data(xn_node);
2668 struct sipe_buddy *sbuddy;
2670 sbuddy = g_hash_table_lookup(sip->buddies, uri);
2671 if (sbuddy && note)
2673 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
2674 sbuddy->annotation = g_strdup(note);
2675 changed = 1;
2678 else if(!strcmp(attrVar, "state"))
2680 xn_node = xmlnode_get_child(xn_category, "state");
2681 if (!xn_node) continue;
2682 xn_node = xmlnode_get_child(xn_node, "availability");
2683 if (!xn_node) continue;
2685 char *data = xmlnode_get_data(xn_node);
2686 int avail = atoi(data);
2688 if (avail < 3000)
2689 activity = "unknown";
2690 else if (avail < 4500)
2691 activity = "available";
2692 else if (avail < 6000)
2693 activity = "idle";
2694 else if (avail < 7500)
2695 activity = "busy";
2696 else if (avail < 9000)
2697 activity = "busy";
2698 else if (avail < 12000)
2699 activity = "dnd";
2700 else if (avail < 18000)
2701 activity = "away";
2702 else
2703 activity = "offline";
2705 changed = 1;
2708 if (changed)
2710 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
2712 xmlnode_free(xn_categories);
2715 static void process_incoming_notify_pidf(struct sipe_account_data *sip, struct sipmsg *msg)
2717 gchar *fromhdr;
2718 gchar *from;
2719 gchar *getbasic = g_strdup("closed");
2720 gchar *activity = g_strdup("available");
2721 xmlnode *pidf;
2722 xmlnode *basicstatus = NULL, *tuple, *status;
2723 gboolean isonline = FALSE;
2725 fromhdr = sipmsg_find_header(msg, "From");
2726 from = parse_from(fromhdr);
2728 if (!from) {
2729 return;
2732 pidf = xmlnode_from_str(msg->body, msg->bodylen);
2733 if (!pidf) {
2734 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",msg->body);
2735 return;
2738 if ((tuple = xmlnode_get_child(pidf, "tuple")))
2740 if ((status = xmlnode_get_child(tuple, "status"))) {
2741 basicstatus = xmlnode_get_child(status, "basic");
2745 if (!basicstatus) {
2746 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
2747 xmlnode_free(pidf);
2748 return;
2751 getbasic = xmlnode_get_data(basicstatus);
2752 if (!getbasic) {
2753 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
2754 xmlnode_free(pidf);
2755 return;
2758 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
2759 if (strstr(getbasic, "open")) {
2760 isonline = TRUE;
2763 xmlnode *display_name_node = xmlnode_get_child(pidf, "display-name");
2764 if (display_name_node) {
2765 PurpleBuddy * buddy = purple_find_buddy (sip->account, from);
2766 char * display_name = xmlnode_get_data(display_name_node);
2767 if (buddy && display_name) {
2768 purple_blist_server_alias_buddy (buddy, g_strdup(display_name));
2772 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
2773 if ((status = xmlnode_get_child(tuple, "status"))) {
2774 if (basicstatus = xmlnode_get_child(status, "activities")) {
2775 if (basicstatus = xmlnode_get_child(basicstatus, "activity")) {
2776 activity = xmlnode_get_data(basicstatus);
2782 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
2784 if (isonline) {
2785 gchar * status_id = NULL;
2786 if (activity) {
2787 if (strstr(activity, "busy")) {
2788 status_id = "busy";
2789 } else if (strstr(activity, "away")) {
2790 status_id = "away";
2794 if (!status_id) {
2795 status_id = "available";
2798 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
2799 purple_prpl_got_user_status(sip->account, from, status_id, NULL);
2800 } else {
2801 purple_prpl_got_user_status(sip->account, from, "offline", NULL);
2804 xmlnode_free(pidf);
2805 g_free(from);
2806 g_free(getbasic);
2807 g_free(activity);
2810 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, struct sipmsg *msg)
2812 const char *availability;
2813 const char *activity;
2814 const char *note = NULL;
2815 const char *activity_name;
2816 gchar *uri;
2818 xmlnode *xn_presentity = xmlnode_from_str(msg->body, msg->bodylen);
2819 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
2820 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
2821 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
2822 xmlnode *xn_note = xmlnode_get_child(xn_userinfo, "note");
2824 uri = g_strdup_printf("sip:%s", xmlnode_get_attrib(xn_presentity, "uri"));
2825 availability = xmlnode_get_attrib(xn_availability, "aggregate");
2826 activity = xmlnode_get_attrib(xn_activity, "aggregate");
2827 if (xn_note) {
2828 note = xmlnode_get_data(xn_note);
2831 int avl = atoi(availability);
2832 int act = atoi(activity);
2834 if (act <= 100)
2835 activity_name = "away";
2836 else if (act <= 150)
2837 activity_name = "out-to-lunch";
2838 else if (act <= 300)
2839 activity_name = "be-right-back";
2840 else if (act <= 400)
2841 activity_name = "available";
2842 else if (act <= 500)
2843 activity_name = "on-the-phone";
2844 else if (act <= 600)
2845 activity_name = "busy";
2846 else
2847 activity_name = "available";
2849 if (avl == 0)
2850 activity_name = "offline";
2852 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
2853 if (sbuddy)
2855 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
2856 sbuddy->annotation = NULL;
2857 if (note) { sbuddy->annotation = g_strdup(note); }
2860 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
2861 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
2862 xmlnode_free(xn_presentity);
2863 g_free(uri);
2866 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2868 char *ctype = sipmsg_find_header(msg, "Content-Type");
2870 purple_debug_info("sipe", "process_incoming_notify: Content-Type: %s\n\n%s\n", ctype, msg->body);
2872 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
2873 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
2875 const char *content = msg->body;
2876 unsigned length = msg->bodylen;
2877 PurpleMimeDocument *mime = NULL;
2879 if (strstr(ctype, "multipart"))
2881 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
2882 mime = purple_mime_document_parse(doc);
2883 GList* parts = purple_mime_document_get_parts(mime);
2884 content = purple_mime_part_get_data(parts->data);
2885 length = purple_mime_part_get_length(parts->data);
2886 g_free(doc);
2888 char *subscription_state = sipmsg_find_header(msg, "subscription-state");
2889 if (strstr(subscription_state, "active")){
2890 process_incoming_notify_rlmi(sip, content, length);
2892 else if (strstr(subscription_state, "terminated")){
2893 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n",subscription_state);
2894 process_incoming_notify_rlmi_resub(sip,content,length);
2897 if (mime)
2899 purple_mime_document_free(mime);
2902 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
2904 process_incoming_notify_msrtc(sip, msg);
2906 else
2908 process_incoming_notify_pidf(sip, msg);
2910 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2913 static gchar* gen_xpidf(struct sipe_account_data *sip)
2915 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
2916 "<presence>\r\n"
2917 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
2918 "<display name=\"sip:%s\"/>\r\n"
2919 "<atom id=\"1234\">\r\n"
2920 "<address uri=\"sip:%s\">\r\n"
2921 "<status status=\"%s\"/>\r\n"
2922 "</address>\r\n"
2923 "</atom>\r\n"
2924 "</presence>\r\n",
2925 sip->username,
2926 sip->username,
2927 sip->username,
2928 sip->status);
2929 return doc;
2934 static gchar* gen_pidf(struct sipe_account_data *sip)
2936 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
2937 "<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"
2938 "<tuple id=\"0\">\r\n"
2939 "<status>\r\n"
2940 "<basic>open</basic>\r\n"
2941 "<ep:activities>\r\n"
2942 " <ep:activity>%s</ep:activity>\r\n"
2943 "</ep:activities>"
2944 "</status>\r\n"
2945 "</tuple>\r\n"
2946 "<ci:display-name>%s</ci:display-name>\r\n"
2947 "</presence>",
2948 sip->username,
2949 sip->status,
2950 sip->username);
2951 return doc;
2954 static void send_clear_notes(struct sipe_account_data *sip)
2958 static gboolean
2959 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2961 if (msg->response == 488) {
2962 sip->presence_method_version = 1;
2963 send_presence_info(sip);
2965 return TRUE;
2968 static void send_presence_info_v0(struct sipe_account_data *sip, char * note)
2970 int availability, activity;
2971 availability = 300; // online
2972 activity = 400; // Available
2973 if (!strcmp(sip->status, "away")) {
2974 activity = 100;
2975 } else if (!strcmp(sip->status, "out-to-lunch")) {
2976 activity = 150;
2977 } else if (!strcmp(sip->status, "be-right-back")) {
2978 activity = 300;
2979 } else if (!strcmp(sip->status, "on-the-phone")) {
2980 activity = 500;
2981 } else if (!strcmp(sip->status, "do-not-disturb")) {
2982 activity = 600;
2983 } else if (!strcmp(sip->status, "busy")) {
2984 activity = 600;
2985 } else if (!strcmp(sip->status, "invisible")) {
2986 availability = 0; // offline
2987 activity = 100;
2990 gchar *name = g_strdup_printf("sip: sip:%s", sip->username);
2991 //@TODO: send user data - state; add hostname in upper case
2992 gchar * body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
2993 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
2994 g_free(name);
2995 g_free(body);
2998 static gboolean
2999 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3001 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3002 if (msg->response == 200) {
3003 sip->status_version = 0;
3004 send_presence_info(sip);
3006 return TRUE;
3009 static gboolean
3010 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3012 if (msg->response == 409) {
3013 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3014 // TODO need to parse the version #'s?
3015 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3016 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3018 gchar *tmp = get_contact(sip);
3019 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
3020 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3022 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3024 g_free(tmp);
3025 g_free(hdr);
3026 g_free(uri);
3027 g_free(doc);
3029 return TRUE;
3032 static void send_presence_info_v1(struct sipe_account_data *sip, char * note)
3034 int code;
3035 if (!strcmp(sip->status, "away")) {
3036 code = 12000;
3037 } else if (!strcmp(sip->status, "busy")) {
3038 code = 6000;
3039 } else {
3040 // Available
3041 code = 3000;
3044 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3045 gchar *doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
3046 sip->status_version, code,
3047 sip->status_version, code,
3048 sip->status_version, note ? note : "",
3049 sip->status_version, note ? note : "",
3050 sip->status_version, note ? note : ""
3052 sip->status_version++;
3054 gchar *tmp = get_contact(sip);
3055 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
3056 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3058 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
3060 g_free(tmp);
3061 g_free(hdr);
3062 g_free(uri);
3063 g_free(doc);
3066 static void send_presence_info(struct sipe_account_data *sip)
3068 PurpleStatus * status = purple_account_get_active_status(sip->account);
3069 if (!status) return;
3071 gchar *note = g_strdup(purple_status_get_attr_string(status, "message"));
3073 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
3074 if (sip->presence_method_version != 1) {
3075 send_presence_info_v0(sip, note);
3076 } else {
3077 send_presence_info_v1(sip, note);
3081 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
3083 gboolean found = FALSE;
3084 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
3085 if (msg->response == 0) { /* request */
3086 if (!strcmp(msg->method, "MESSAGE")) {
3087 process_incoming_message(sip, msg);
3088 found = TRUE;
3089 } else if (!strcmp(msg->method, "NOTIFY")) {
3090 purple_debug_info("sipe","send->process_incoming_notify\n");
3091 process_incoming_notify(sip, msg);
3092 found = TRUE;
3093 } else if (!strcmp(msg->method, "BENOTIFY")) {
3094 purple_debug_info("sipe","send->process_incoming_benotify\n");
3095 process_incoming_benotify(sip, msg);
3096 found = TRUE;
3097 } else if (!strcmp(msg->method, "INVITE")) {
3098 process_incoming_invite(sip, msg);
3099 found = TRUE;
3100 } else if (!strcmp(msg->method, "INFO")) {
3101 // TODO needs work
3102 gchar * from = parse_from(sipmsg_find_header(msg, "From"));
3103 if (from) {
3104 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3106 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3107 found = TRUE;
3108 } else if (!strcmp(msg->method, "ACK")) {
3109 // ACK's don't need any response
3110 found = TRUE;
3111 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
3112 // LCS 2005 sends us these - just respond 200 OK
3113 found = TRUE;
3114 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3115 } else if (!strcmp(msg->method, "BYE")) {
3116 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3118 gchar * from = parse_from(sipmsg_find_header(msg, "From"));
3119 struct sip_im_session * session = find_im_session (sip, from);
3120 g_free(from);
3122 if (session) {
3123 // TODO Let the user know the other user left the conversation?
3124 im_session_destroy(sip, session);
3127 found = TRUE;
3128 } else {
3129 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3131 } else { /* response */
3132 struct transaction *trans = transactions_find(sip, msg);
3133 if (trans) {
3134 if (msg->response == 407) {
3135 gchar *resend, *auth, *ptmp;
3137 if (sip->proxy.retries > 30) return;
3138 sip->proxy.retries++;
3139 /* do proxy authentication */
3141 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
3143 fill_auth(sip, ptmp, &sip->proxy);
3144 auth = auth_header(sip, &sip->proxy, trans->msg);
3145 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3146 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
3147 g_free(auth);
3148 resend = sipmsg_to_string(trans->msg);
3149 /* resend request */
3150 sendout_pkt(sip->gc, resend);
3151 g_free(resend);
3152 } else {
3153 if (msg->response == 100 || msg->response == 180) {
3154 /* ignore provisional response */
3155 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
3156 } else {
3157 sip->proxy.retries = 0;
3158 if (!strcmp(trans->msg->method, "REGISTER")) {
3159 if (msg->response == 401)
3161 sip->registrar.retries++;
3162 sip->registrar.expires = 0;
3164 else
3166 sip->registrar.retries = 0;
3168 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
3169 } else {
3170 if (msg->response == 401) {
3171 gchar *resend, *auth, *ptmp;
3173 if (sip->registrar.retries > 4) return;
3174 sip->registrar.retries++;
3176 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3177 ptmp = sipmsg_find_auth_header(msg, "NTLM");
3178 } else {
3179 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
3182 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
3184 fill_auth(sip, ptmp, &sip->registrar);
3185 auth = auth_header(sip, &sip->registrar, trans->msg);
3186 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
3187 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
3189 //sipmsg_remove_header(trans->msg, "Authorization");
3190 //sipmsg_add_header(trans->msg, "Authorization", auth);
3191 g_free(auth);
3192 resend = sipmsg_to_string(trans->msg);
3193 /* resend request */
3194 sendout_pkt(sip->gc, resend);
3195 g_free(resend);
3199 if (trans->callback) {
3200 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
3201 /* call the callback to process response*/
3202 (trans->callback)(sip, msg, trans);
3204 /* Not sure if this is needed or what needs to be done
3205 but transactions seem to be removed prematurely so
3206 this only removes them if the response is 200 OK */
3207 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
3208 /*Has a bug and it's unneccesary*/
3209 /*transactions_remove(sip, trans);*/
3213 found = TRUE;
3214 } else {
3215 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
3218 if (!found) {
3219 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
3223 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
3225 char *cur;
3226 char *dummy;
3227 struct sipmsg *msg;
3228 int restlen;
3229 cur = conn->inbuf;
3231 /* according to the RFC remove CRLF at the beginning */
3232 while (*cur == '\r' || *cur == '\n') {
3233 cur++;
3235 if (cur != conn->inbuf) {
3236 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
3237 conn->inbufused = strlen(conn->inbuf);
3240 /* Received a full Header? */
3241 sip->processing_input = TRUE;
3242 while (sip->processing_input &&
3243 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
3244 time_t currtime = time(NULL);
3245 cur += 2;
3246 cur[0] = '\0';
3247 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
3248 msg = sipmsg_parse_header(conn->inbuf);
3249 cur[0] = '\r';
3250 cur += 2;
3251 restlen = conn->inbufused - (cur - conn->inbuf);
3252 if (restlen >= msg->bodylen) {
3253 dummy = g_malloc(msg->bodylen + 1);
3254 memcpy(dummy, cur, msg->bodylen);
3255 dummy[msg->bodylen] = '\0';
3256 msg->body = dummy;
3257 cur += msg->bodylen;
3258 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
3259 conn->inbufused = strlen(conn->inbuf);
3260 } else {
3261 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
3262 restlen, msg->bodylen, (int)strlen(conn->inbuf));
3263 sipmsg_free(msg);
3264 return;
3267 /*if (msg->body) {
3268 purple_debug_info("sipe", "body:\n%s", msg->body);
3271 // Verify the signature before processing it
3272 if (sip->registrar.ntlm_key) {
3273 struct sipmsg_breakdown msgbd;
3274 msgbd.msg = msg;
3275 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
3276 gchar * signature_input_str = sipmsg_breakdown_get_string(&msgbd);
3277 gchar * signature;
3278 if (signature_input_str != NULL) {
3279 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
3282 gchar * rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
3284 if (signature != NULL) {
3285 if (rspauth != NULL) {
3286 if (purple_ntlm_verify_signature (signature, rspauth)) {
3287 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
3288 process_input_message(sip, msg);
3289 } else {
3290 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
3291 purple_connection_error(sip->gc, _("Invalid message signature received"));
3292 sip->gc->wants_to_die = TRUE;
3294 } else if (msg->response == 401) {
3295 purple_connection_error(sip->gc, _("Wrong Password"));
3296 sip->gc->wants_to_die = TRUE;
3300 sipmsg_breakdown_free(&msgbd);
3301 } else {
3302 process_input_message(sip, msg);
3307 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
3309 PurpleConnection *gc = data;
3310 struct sipe_account_data *sip = gc->proto_data;
3311 struct sipmsg *msg;
3312 int len;
3313 time_t currtime;
3315 static char buffer[65536];
3316 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
3317 buffer[len] = '\0';
3318 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
3319 msg = sipmsg_parse_msg(buffer);
3320 if (msg) process_input_message(sip, msg);
3324 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
3326 struct sipe_account_data *sip = gc->proto_data;
3327 PurpleSslConnection *gsc = sip->gsc;
3329 purple_debug_error("sipe", "%s",debug);
3330 purple_connection_error(gc, msg);
3332 /* Invalidate this connection. Next send will open a new one */
3333 if (gsc) {
3334 connection_remove(sip, gsc->fd);
3335 purple_ssl_close(gsc);
3337 sip->gsc = NULL;
3338 sip->fd = -1;
3341 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
3343 PurpleConnection *gc = data;
3344 struct sipe_account_data *sip;
3345 struct sip_connection *conn;
3346 int readlen, len;
3347 gboolean firstread = TRUE;
3349 /* NOTE: This check *IS* necessary */
3350 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
3351 purple_ssl_close(gsc);
3352 return;
3355 sip = gc->proto_data;
3356 conn = connection_find(sip, gsc->fd);
3357 if (conn == NULL) {
3358 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
3359 gc->wants_to_die = TRUE;
3360 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
3361 return;
3364 /* Read all available data from the SSL connection */
3365 do {
3366 /* Increase input buffer size as needed */
3367 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
3368 conn->inbuflen += SIMPLE_BUF_INC;
3369 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
3370 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
3373 /* Try to read as much as there is space left in the buffer */
3374 readlen = conn->inbuflen - conn->inbufused - 1;
3375 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
3377 if (len < 0 && errno == EAGAIN) {
3378 /* Try again later */
3379 return;
3380 } else if (len < 0) {
3381 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
3382 return;
3383 } else if (firstread && (len == 0)) {
3384 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
3385 return;
3388 conn->inbufused += len;
3389 firstread = FALSE;
3391 /* Equivalence indicates that there is possibly more data to read */
3392 } while (len == readlen);
3394 conn->inbuf[conn->inbufused] = '\0';
3395 process_input(sip, conn);
3399 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
3401 PurpleConnection *gc = data;
3402 struct sipe_account_data *sip = gc->proto_data;
3403 int len;
3404 struct sip_connection *conn = connection_find(sip, source);
3405 if (!conn) {
3406 purple_debug_error("sipe", "Connection not found!\n");
3407 return;
3410 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
3411 conn->inbuflen += SIMPLE_BUF_INC;
3412 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
3415 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
3417 if (len < 0 && errno == EAGAIN)
3418 return;
3419 else if (len <= 0) {
3420 purple_debug_info("sipe", "sipe_input_cb: read error\n");
3421 connection_remove(sip, source);
3422 if (sip->fd == source) sip->fd = -1;
3423 return;
3426 conn->inbufused += len;
3427 conn->inbuf[conn->inbufused] = '\0';
3429 process_input(sip, conn);
3432 /* Callback for new connections on incoming TCP port */
3433 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
3435 PurpleConnection *gc = data;
3436 struct sipe_account_data *sip = gc->proto_data;
3437 struct sip_connection *conn;
3439 int newfd = accept(source, NULL, NULL);
3441 conn = connection_create(sip, newfd);
3443 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
3446 static void login_cb(gpointer data, gint source, const gchar *error_message)
3448 PurpleConnection *gc = data;
3449 struct sipe_account_data *sip;
3450 struct sip_connection *conn;
3452 if (!PURPLE_CONNECTION_IS_VALID(gc))
3454 if (source >= 0)
3455 close(source);
3456 return;
3459 if (source < 0) {
3460 purple_connection_error(gc, _("Could not connect"));
3461 return;
3464 sip = gc->proto_data;
3465 sip->fd = source;
3466 sip->last_keepalive = time(NULL);
3468 conn = connection_create(sip, source);
3470 sip->registertimeout = purple_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
3471 do_register(sip);
3473 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
3476 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
3478 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
3479 if (sip == NULL) return;
3481 sip->registertimeout = purple_timeout_add((rand()%100) + 10*1000, (GSourceFunc)subscribe_timeout, sip);
3482 do_register(sip);
3485 static guint sipe_ht_hash_nick(const char *nick)
3487 char *lc = g_utf8_strdown(nick, -1);
3488 guint bucket = g_str_hash(lc);
3489 g_free(lc);
3491 return bucket;
3494 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
3496 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
3499 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
3501 struct sipe_account_data *sip = (struct sipe_account_data*) data;
3503 sip->listen_data = NULL;
3505 if (listenfd == -1) {
3506 purple_connection_error(sip->gc, _("Could not create listen socket"));
3507 return;
3510 sip->fd = listenfd;
3512 sip->listenport = purple_network_get_port_from_fd(sip->fd);
3513 sip->listenfd = sip->fd;
3515 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
3517 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
3518 sip->registertimeout = purple_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
3519 do_register(sip);
3522 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
3524 struct sipe_account_data *sip = (struct sipe_account_data*) data;
3525 int addr_size;
3527 sip->query_data = NULL;
3529 if (!hosts || !hosts->data) {
3530 purple_connection_error(sip->gc, _("Couldn't resolve host"));
3531 return;
3534 addr_size = GPOINTER_TO_INT(hosts->data);
3535 hosts = g_slist_remove(hosts, hosts->data);
3536 memcpy(&(sip->serveraddr), hosts->data, addr_size);
3537 g_free(hosts->data);
3538 hosts = g_slist_remove(hosts, hosts->data);
3539 while (hosts) {
3540 hosts = g_slist_remove(hosts, hosts->data);
3541 g_free(hosts->data);
3542 hosts = g_slist_remove(hosts, hosts->data);
3545 /* create socket for incoming connections */
3546 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
3547 sipe_udp_host_resolved_listen_cb, sip);
3548 if (sip->listen_data == NULL) {
3549 purple_connection_error(sip->gc, _("Could not create listen socket"));
3550 return;
3554 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
3555 gpointer data)
3557 PurpleConnection *gc = data;
3558 struct sipe_account_data *sip;
3560 /* If the connection is already disconnected, we don't need to do anything else */
3561 if (!PURPLE_CONNECTION_IS_VALID(gc))
3562 return;
3564 sip = gc->proto_data;
3565 sip->fd = -1;
3566 sip->gsc = NULL;
3568 switch(error) {
3569 case PURPLE_SSL_CONNECT_FAILED:
3570 purple_connection_error(gc, _("Connection Failed"));
3571 break;
3572 case PURPLE_SSL_HANDSHAKE_FAILED:
3573 purple_connection_error(gc, _("SSL Handshake Failed"));
3574 break;
3578 static void
3579 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
3581 struct sipe_account_data *sip = (struct sipe_account_data*) data;
3582 PurpleProxyConnectData *connect_data;
3584 sip->listen_data = NULL;
3586 sip->listenfd = listenfd;
3587 if (sip->listenfd == -1) {
3588 purple_connection_error(sip->gc, _("Could not create listen socket"));
3589 return;
3592 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
3593 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
3594 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
3595 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
3596 sipe_newconn_cb, sip->gc);
3597 purple_debug_info("sipe", "connecting to %s port %d\n",
3598 sip->realhostname, sip->realport);
3599 /* open tcp connection to the server */
3600 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
3601 sip->realport, login_cb, sip->gc);
3603 if (connect_data == NULL) {
3604 purple_connection_error(sip->gc, _("Couldn't create socket"));
3609 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
3611 PurpleAccount *account = sip->account;
3612 PurpleConnection *gc = sip->gc;
3614 if (purple_account_get_bool(account, "useport", FALSE)) {
3615 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
3616 port = purple_account_get_int(account, "port", 0);
3617 } else {
3618 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
3621 sip->realhostname = hostname;
3622 sip->realport = port;
3624 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
3625 hostname, port);
3627 /* TODO: is there a good default grow size? */
3628 if (sip->transport != SIPE_TRANSPORT_UDP)
3629 sip->txbuf = purple_circ_buffer_new(0);
3631 if (sip->transport == SIPE_TRANSPORT_TLS) {
3632 /* SSL case */
3633 if (!purple_ssl_is_supported()) {
3634 gc->wants_to_die = TRUE;
3635 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
3636 return;
3639 purple_debug_info("sipe", "using SSL\n");
3641 sip->gsc = purple_ssl_connect(account, hostname, port,
3642 login_cb_ssl, sipe_ssl_connect_failure, gc);
3643 if (sip->gsc == NULL) {
3644 purple_connection_error(gc, _("Could not create SSL context"));
3645 return;
3647 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
3648 /* UDP case */
3649 purple_debug_info("sipe", "using UDP\n");
3651 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
3652 if (sip->query_data == NULL) {
3653 purple_connection_error(gc, _("Could not resolve hostname"));
3655 } else {
3656 /* TCP case */
3657 purple_debug_info("sipe", "using TCP\n");
3658 /* create socket for incoming connections */
3659 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
3660 sipe_tcp_connect_listen_cb, sip);
3661 if (sip->listen_data == NULL) {
3662 purple_connection_error(gc, _("Could not create listen socket"));
3663 return;
3668 /* Service list for autodection */
3669 static const struct sipe_service_data service_autodetect[] = {
3670 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
3671 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
3672 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
3673 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
3674 { NULL, NULL, 0 }
3677 /* Service list for SSL/TLS */
3678 static const struct sipe_service_data service_tls[] = {
3679 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
3680 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
3681 { NULL, NULL, 0 }
3684 /* Service list for TCP */
3685 static const struct sipe_service_data service_tcp[] = {
3686 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
3687 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
3688 { NULL, NULL, 0 }
3691 /* Service list for UDP */
3692 static const struct sipe_service_data service_udp[] = {
3693 { "sip", "udp", SIPE_TRANSPORT_UDP },
3694 { NULL, NULL, 0 }
3697 static void srvresolved(PurpleSrvResponse *, int, gpointer);
3698 static void resolve_next_service(struct sipe_account_data *sip,
3699 const struct sipe_service_data *start)
3701 if (start) {
3702 sip->service_data = start;
3703 } else {
3704 sip->service_data++;
3705 if (sip->service_data->service == NULL) {
3706 /* Try connecting to the SIP hostname directly */
3707 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
3708 if (sip->auto_transport) {
3709 // If SSL is supported, default to using it; OCS servers aren't configured
3710 // by default to accept TCP
3711 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
3712 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
3713 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
3716 gchar * hostname = g_strdup(sip->sipdomain);
3717 create_connection(sip, hostname, 0);
3718 return;
3722 /* Try to resolve next service */
3723 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
3724 sip->service_data->transport,
3725 sip->sipdomain,
3726 srvresolved, sip);
3729 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
3731 struct sipe_account_data *sip = data;
3733 sip->srv_query_data = NULL;
3735 /* find the host to connect to */
3736 if (results) {
3737 gchar *hostname = g_strdup(resp->hostname);
3738 int port = resp->port;
3739 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
3740 hostname, port);
3741 g_free(resp);
3743 sip->transport = sip->service_data->type;
3745 create_connection(sip, hostname, port);
3746 } else {
3747 resolve_next_service(sip, NULL);
3751 static void sipe_login(PurpleAccount *account)
3753 PurpleConnection *gc;
3754 struct sipe_account_data *sip;
3755 gchar **userserver;
3756 const char *transport;
3758 const char *username = purple_account_get_username(account);
3759 gc = purple_account_get_connection(account);
3761 if (strpbrk(username, " \t\v\r\n") != NULL) {
3762 gc->wants_to_die = TRUE;
3763 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
3764 return;
3767 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
3768 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
3769 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
3770 sip->gc = gc;
3771 sip->account = account;
3772 sip->registerexpire = 900;
3774 userserver = g_strsplit(username, "@", 2);
3775 purple_connection_set_display_name(gc, userserver[0]);
3776 sip->username = g_strdup(g_strjoin("@", userserver[0], userserver[1], NULL));
3777 sip->sipdomain = g_strdup(userserver[1]);
3778 sip->password = g_strdup(purple_connection_get_password(gc));
3779 g_strfreev(userserver);
3781 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
3783 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
3785 /* TODO: Set the status correctly. */
3786 sip->status = g_strdup("available");
3788 transport = purple_account_get_string(account, "transport", "auto");
3789 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
3790 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
3791 SIPE_TRANSPORT_UDP;
3793 if (purple_account_get_bool(account, "useproxy", FALSE)) {
3794 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
3795 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
3796 } else if (strcmp(transport, "auto") == 0) {
3797 sip->auto_transport = TRUE;
3798 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
3799 } else if (strcmp(transport, "tls") == 0) {
3800 resolve_next_service(sip, service_tls);
3801 } else if (strcmp(transport, "tcp") == 0) {
3802 resolve_next_service(sip, service_tcp);
3803 } else {
3804 resolve_next_service(sip, service_udp);
3808 static void sipe_connection_cleanup(struct sipe_account_data *sip)
3810 connection_free_all(sip);
3812 if (sip->query_data != NULL)
3813 purple_dnsquery_destroy(sip->query_data);
3814 sip->query_data == NULL;
3816 if (sip->srv_query_data != NULL)
3817 purple_srv_cancel(sip->srv_query_data);
3818 sip->srv_query_data = NULL;
3820 if (sip->listen_data != NULL)
3821 purple_network_listen_cancel(sip->listen_data);
3822 sip->listen_data = NULL;
3824 if (sip->gsc != NULL)
3825 purple_ssl_close(sip->gsc);
3826 sip->gsc = NULL;
3828 sipe_auth_free(&sip->registrar);
3829 sipe_auth_free(&sip->proxy);
3831 if (sip->txbuf)
3832 purple_circ_buffer_destroy(sip->txbuf);
3833 sip->txbuf = NULL;
3835 g_free(sip->realhostname);
3836 sip->realhostname = NULL;
3838 if (sip->listenpa)
3839 purple_input_remove(sip->listenpa);
3840 sip->listenpa = 0;
3841 if (sip->tx_handler)
3842 purple_input_remove(sip->tx_handler);
3843 sip->tx_handler = 0;
3844 if (sip->resendtimeout)
3845 purple_timeout_remove(sip->resendtimeout);
3846 sip->resendtimeout = 0;
3847 if (sip->registertimeout)
3848 purple_timeout_remove(sip->registertimeout);
3849 sip->registertimeout = 0;
3851 sip->fd = -1;
3852 sip->processing_input = FALSE;
3855 static void sipe_close(PurpleConnection *gc)
3857 struct sipe_account_data *sip = gc->proto_data;
3859 if (sip) {
3860 /* leave all conversations */
3861 im_session_close_all(sip);
3863 /* unregister */
3864 do_register_exp(sip, 0);
3866 sipe_connection_cleanup(sip);
3867 g_free(sip->sipdomain);
3868 g_free(sip->username);
3869 g_free(sip->password);
3871 g_free(gc->proto_data);
3872 gc->proto_data = NULL;
3875 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
3877 PurpleAccount *acct = purple_connection_get_account(gc);
3878 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
3879 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
3880 if (conv == NULL)
3881 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
3882 purple_conversation_present(conv);
3883 g_free(id);
3886 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
3889 purple_blist_request_add_buddy(purple_connection_get_account(gc),
3890 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
3893 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
3895 PurpleNotifySearchResults *results;
3896 PurpleNotifySearchColumn *column;
3897 xmlnode *searchResults;
3898 xmlnode *mrow;
3900 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
3901 if (!searchResults) {
3902 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
3903 return FALSE;
3906 results = purple_notify_searchresults_new();
3908 if (results == NULL) {
3909 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
3910 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
3912 xmlnode_free(searchResults);
3913 return FALSE;
3916 column = purple_notify_searchresults_column_new(_("User Name"));
3917 purple_notify_searchresults_column_add(results, column);
3919 column = purple_notify_searchresults_column_new(_("Name"));
3920 purple_notify_searchresults_column_add(results, column);
3922 column = purple_notify_searchresults_column_new(_("Company"));
3923 purple_notify_searchresults_column_add(results, column);
3925 column = purple_notify_searchresults_column_new(_("Country"));
3926 purple_notify_searchresults_column_add(results, column);
3928 column = purple_notify_searchresults_column_new(_("Email"));
3929 purple_notify_searchresults_column_add(results, column);
3931 int match_count = 0;
3932 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
3933 GList *row = NULL;
3935 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
3936 row = g_list_append(row, g_strdup(uri_parts[1]));
3937 g_strfreev(uri_parts);
3939 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
3940 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
3941 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
3942 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
3944 purple_notify_searchresults_row_add(results, row);
3945 match_count++;
3948 gboolean more = FALSE;
3949 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
3950 char *data = xmlnode_get_data_unescaped(mrow);
3951 more = (g_strcasecmp(data, "true") == 0);
3952 g_free(data);
3955 gchar *secondary = g_strdup_printf(
3956 dngettext(GETTEXT_PACKAGE,
3957 "Found %d contact%s:",
3958 "Found %d contacts%s:", match_count),
3959 match_count, more ? _(" (more matched your query)") : "");
3961 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
3962 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
3963 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
3965 g_free(secondary);
3966 xmlnode_free(searchResults);
3967 return TRUE;
3970 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
3972 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
3973 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
3974 unsigned i = 0;
3976 do {
3977 PurpleRequestField *field = entries->data;
3978 const char *id = purple_request_field_get_id(field);
3979 const char *value = purple_request_field_string_get_value(field);
3981 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
3983 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
3984 } while ((entries = g_list_next(entries)) != NULL);
3985 attrs[i] = NULL;
3987 if (i > 0) {
3988 gchar *query = g_strjoinv(NULL, attrs);
3989 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
3990 send_soap_request_with_cb(gc->proto_data, body,
3991 (TransCallback) process_search_contact_response, NULL);
3992 g_free(body);
3993 g_free(query);
3996 g_strfreev(attrs);
3999 static void sipe_show_find_contact(PurplePluginAction *action)
4001 PurpleConnection *gc = (PurpleConnection *) action->context;
4002 PurpleRequestFields *fields;
4003 PurpleRequestFieldGroup *group;
4004 PurpleRequestField *field;
4006 fields = purple_request_fields_new();
4007 group = purple_request_field_group_new(NULL);
4008 purple_request_fields_add_group(fields, group);
4010 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
4011 purple_request_field_group_add_field(group, field);
4012 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
4013 purple_request_field_group_add_field(group, field);
4014 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
4015 purple_request_field_group_add_field(group, field);
4016 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
4017 purple_request_field_group_add_field(group, field);
4019 purple_request_fields(gc,
4020 _("Search"),
4021 _("Search for a Contact"),
4022 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4023 fields,
4024 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
4025 _("_Cancel"), NULL,
4026 purple_connection_get_account(gc), NULL, NULL, gc);
4029 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
4031 PurpleConnection *gc = (PurpleConnection *) context;
4032 struct sipe_account_data *sip = gc->proto_data;
4033 GList *menu = NULL;
4034 PurplePluginAction *act;
4036 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
4037 menu = g_list_prepend(menu, act);
4039 menu = g_list_reverse(menu);
4041 return menu;
4044 /* not needed since privacy is checked for every subscribe */
4045 static void dummy_add_deny(PurpleConnection *gc, const char *name) {
4048 static void dummy_permit_deny(PurpleConnection *gc)
4052 static gboolean sipe_plugin_load(PurplePlugin *plugin)
4054 return TRUE;
4058 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
4060 return TRUE;
4064 static void sipe_plugin_destroy(PurplePlugin *plugin)
4068 static char *sipe_status_text(PurpleBuddy *buddy)
4070 struct sipe_account_data *sip;
4071 struct sipe_buddy *sbuddy;
4073 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
4074 if (sip) //happens on pidgin exit
4076 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
4077 if (sbuddy && sbuddy->annotation)
4079 return g_strdup(sbuddy->annotation);
4082 else
4084 return NULL;
4088 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
4090 char *annotation = sipe_status_text(buddy);
4092 if (annotation)
4094 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
4095 g_free(annotation);
4099 static PurplePlugin *my_protocol = NULL;
4101 static PurplePluginProtocolInfo prpl_info =
4104 NULL, /* user_splits */
4105 NULL, /* protocol_options */
4106 NO_BUDDY_ICONS, /* icon_spec */
4107 sipe_list_icon, /* list_icon */
4108 NULL, /* list_emblems */
4109 sipe_status_text, /* status_text */
4110 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
4111 sipe_status_types, /* away_states */
4112 NULL, /* blist_node_menu */
4113 NULL, /* chat_info */
4114 NULL, /* chat_info_defaults */
4115 sipe_login, /* login */
4116 sipe_close, /* close */
4117 sipe_im_send, /* send_im */
4118 NULL, /* set_info */ // TODO maybe
4119 sipe_send_typing, /* send_typing */
4120 NULL, /* get_info */ // TODO maybe
4121 sipe_set_status, /* set_status */
4122 NULL, /* set_idle */
4123 NULL, /* change_passwd */
4124 sipe_add_buddy, /* add_buddy */
4125 NULL, /* add_buddies */
4126 sipe_remove_buddy, /* remove_buddy */
4127 NULL, /* remove_buddies */
4128 sipe_add_permit, /* add_permit */
4129 sipe_add_deny, /* add_deny */
4130 sipe_add_deny, /* rem_permit */
4131 sipe_add_permit, /* rem_deny */
4132 dummy_permit_deny, /* set_permit_deny */
4133 NULL, /* join_chat */
4134 NULL, /* reject_chat */
4135 NULL, /* get_chat_name */
4136 NULL, /* chat_invite */
4137 NULL, /* chat_leave */
4138 NULL, /* chat_whisper */
4139 NULL, /* chat_send */
4140 sipe_keep_alive, /* keepalive */
4141 NULL, /* register_user */
4142 NULL, /* get_cb_info */ // deprecated
4143 NULL, /* get_cb_away */ // deprecated
4144 sipe_alias_buddy, /* alias_buddy */
4145 sipe_group_buddy, /* group_buddy */
4146 sipe_rename_group, /* rename_group */
4147 NULL, /* buddy_free */
4148 sipe_convo_closed, /* convo_closed */
4149 purple_normalize_nocase, /* normalize */
4150 NULL, /* set_buddy_icon */
4151 sipe_remove_group, /* remove_group */
4152 NULL, /* get_cb_real_name */ // TODO?
4153 NULL, /* set_chat_topic */
4154 NULL, /* find_blist_chat */
4155 NULL, /* roomlist_get_list */
4156 NULL, /* roomlist_cancel */
4157 NULL, /* roomlist_expand_category */
4158 NULL, /* can_receive_file */
4159 NULL, /* send_file */
4160 NULL, /* new_xfer */
4161 NULL, /* offline_message */
4162 NULL, /* whiteboard_prpl_ops */
4163 sipe_send_raw, /* send_raw */
4167 static PurplePluginInfo info = {
4168 PURPLE_PLUGIN_MAGIC,
4169 PURPLE_MAJOR_VERSION,
4170 PURPLE_MINOR_VERSION,
4171 PURPLE_PLUGIN_PROTOCOL, /**< type */
4172 NULL, /**< ui_requirement */
4173 0, /**< flags */
4174 NULL, /**< dependencies */
4175 PURPLE_PRIORITY_DEFAULT, /**< priority */
4176 "prpl-sipe", /**< id */
4177 "Microsoft LCS/OCS", /**< name */
4178 VERSION, /**< version */
4179 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
4180 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
4181 "Anibal Avelar <avelar@gmail.com>, " /**< author */
4182 "Gabriel Burt <gburt@novell.com>", /**< author */
4183 PURPLE_WEBSITE, /**< homepage */
4184 sipe_plugin_load, /**< load */
4185 sipe_plugin_unload, /**< unload */
4186 sipe_plugin_destroy, /**< destroy */
4187 NULL, /**< ui_info */
4188 &prpl_info, /**< extra_info */
4189 NULL,
4190 sipe_actions,
4191 NULL,
4192 NULL,
4193 NULL,
4194 NULL
4197 static void init_plugin(PurplePlugin *plugin)
4199 PurpleAccountUserSplit *split;
4200 PurpleAccountOption *option;
4201 PurpleKeyValuePair *kvp;
4203 #ifdef ENABLE_NLS
4204 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
4205 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
4206 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
4207 #endif
4209 purple_plugin_register(plugin);
4211 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
4212 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4213 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
4214 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4216 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
4217 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4218 // Translators: noun (networking port)
4219 option = purple_account_option_int_new(_("Port"), "port", 5061);
4220 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4222 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
4223 purple_account_option_add_list_item(option, _("Auto"), "auto");
4224 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
4225 purple_account_option_add_list_item(option, _("TCP"), "tcp");
4226 purple_account_option_add_list_item(option, _("UDP"), "udp");
4227 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4229 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
4230 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
4232 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
4233 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4235 // TODO commented out so won't show in the preferences until we fix krb message signing
4236 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
4237 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4239 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
4240 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
4241 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4244 option = purple_account_option_string_new(_("Auth User"), "authuser", "");
4245 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4246 option = purple_account_option_string_new(_("Auth Domain"), "authdomain", "");
4247 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4248 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
4249 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4250 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
4251 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
4252 my_protocol = plugin;
4255 /* I had to redefined the function for it load, but works */
4256 gboolean purple_init_plugin(PurplePlugin *plugin){
4257 plugin->info = &(info);
4258 init_plugin((plugin));
4259 sipe_plugin_load((plugin));
4260 return purple_plugin_register(plugin);