Fixed segmentation fault in connection_remove
[siplcs.git] / src / sipe.c
blobb081def300d2e59c3a6d07260244840f6fa97582
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 #else
39 #ifdef _DLL
40 #define _WS2TCPIP_H_
41 #define _WINSOCK2API_
42 #define _LIBC_INTERNAL_
43 #endif /* _DLL */
45 #include "internal.h"
46 #endif /* _WIN32 */
48 #include <time.h>
49 #include <stdio.h>
50 #include <errno.h>
51 #include <string.h>
52 #include <glib.h>
54 #ifdef ENABLE_NLS
55 #include <libintl.h>
56 # define _(String) ((const char *) gettext (String))
57 #else
58 # define _(String) ((const char *) (String))
59 #endif /* ENABLE_NLS */
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"
75 #include "sipe.h"
76 #include "sip-ntlm.h"
77 #ifdef USE_KERBEROS
78 #include "sipkrb5.h"
79 #endif /*USE_KERBEROS*/
81 #include "sipmsg.h"
82 #include "sipe-sign.h"
83 #include "dnssrv.h"
84 #include "request.h"
86 /* Keep in sync with sipe_transport_type! */
87 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
88 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
90 static char *gentag()
92 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
95 static gchar *get_epid()
97 return sipe_uuid_get_macaddr();
100 static char *genbranch()
102 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
103 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
104 rand() & 0xFFFF, rand() & 0xFFFF);
107 static char *gencallid()
109 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
110 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
111 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
112 rand() & 0xFFFF, rand() & 0xFFFF);
115 static gchar *find_tag(const gchar *hdr)
117 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
118 if (!tag) {
119 // In case it's at the end and there's no trailing ;
120 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
122 return tag;
126 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
128 return "sipe";
131 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
133 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
134 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
135 gpointer data);
137 static void sipe_close(PurpleConnection *gc);
139 static void sipe_subscribe_to_name(struct sipe_account_data *sip, const char * buddy_name);
140 static void send_presence_info(struct sipe_account_data *sip);
142 static void sendout_pkt(PurpleConnection *gc, const char *buf);
144 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
146 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
147 if (timeout != NULL) {
148 sscanf(timeout, "%u", &sip->keepalive_timeout);
149 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
150 sip->keepalive_timeout);
154 static void sipe_keep_alive(PurpleConnection *gc)
156 struct sipe_account_data *sip = gc->proto_data;
157 if (sip->transport == SIPE_TRANSPORT_UDP) {
158 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
159 gchar buf[2] = {0, 0};
160 purple_debug_info("sipe", "sending keep alive\n");
161 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
162 } else {
163 time_t now = time(NULL);
164 if ((sip->keepalive_timeout > 0) &&
165 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
166 #if PURPLE_VERSION_CHECK(2,4,0)
167 && ((now - gc->last_received) >= sip->keepalive_timeout)
168 #endif
170 purple_debug_info("sipe", "sending keep alive\n");
171 sendout_pkt(gc, "\r\n\r\n");
172 sip->last_keepalive = now;
177 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
179 struct sip_connection *ret = NULL;
180 GSList *entry = sip->openconns;
181 while (entry) {
182 ret = entry->data;
183 if (ret->fd == fd) return ret;
184 entry = entry->next;
186 return NULL;
189 static struct sipe_watcher *watcher_find(struct sipe_account_data *sip,
190 const gchar *name)
192 struct sipe_watcher *watcher;
193 GSList *entry = sip->watcher;
194 while (entry) {
195 watcher = entry->data;
196 if (!strcmp(name, watcher->name)) return watcher;
197 entry = entry->next;
199 return NULL;
202 static struct sipe_watcher *watcher_create(struct sipe_account_data *sip,
203 const gchar *name, const gchar *callid, const gchar *ourtag,
204 const gchar *theirtag, gboolean needsxpidf)
206 struct sipe_watcher *watcher = g_new0(struct sipe_watcher, 1);
207 watcher->name = g_strdup(name);
208 watcher->dialog.callid = g_strdup(callid);
209 watcher->dialog.ourtag = g_strdup(ourtag);
210 watcher->dialog.theirtag = g_strdup(theirtag);
211 watcher->needsxpidf = needsxpidf;
212 sip->watcher = g_slist_append(sip->watcher, watcher);
213 return watcher;
216 static void watcher_remove(struct sipe_account_data *sip, const gchar *name)
218 struct sipe_watcher *watcher = watcher_find(sip, name);
219 sip->watcher = g_slist_remove(sip->watcher, watcher);
220 g_free(watcher->name);
221 g_free(watcher->dialog.callid);
222 g_free(watcher->dialog.ourtag);
223 g_free(watcher->dialog.theirtag);
224 g_free(watcher);
227 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
229 struct sip_connection *ret = g_new0(struct sip_connection, 1);
230 ret->fd = fd;
231 sip->openconns = g_slist_append(sip->openconns, ret);
232 return ret;
235 static void connection_remove(struct sipe_account_data *sip, int fd)
237 struct sip_connection *conn = connection_find(sip, fd);
238 sip->openconns = g_slist_remove(sip->openconns, conn);
239 if (conn && conn->inputhandler) purple_input_remove(conn->inputhandler);
240 g_free(conn->inbuf);
241 g_free(conn);
244 static void connection_free_all(struct sipe_account_data *sip)
246 struct sip_connection *ret = NULL;
247 GSList *entry = sip->openconns;
248 while (entry) {
249 ret = entry->data;
250 connection_remove(sip, ret->fd);
251 entry = sip->openconns;
255 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
257 const gchar *method = msg->method;
258 const gchar *target = msg->target;
259 gchar noncecount[9];
260 gchar *response;
261 gchar *ret;
262 gchar *tmp;
263 const char *authdomain;
264 const char *authuser;
265 const char *krb5_realm;
266 const char *host;
267 gchar *krb5_token = NULL;
269 authdomain = purple_account_get_string(sip->account, "authdomain", "");
270 authuser = purple_account_get_string(sip->account, "authuser", sip->username);
272 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
273 // and do error checking
275 // KRB realm should always be uppercase
276 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
278 if (sip->realhostname) {
279 host = sip->realhostname;
280 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
281 host = purple_account_get_string(sip->account, "proxy", "");
282 } else {
283 host = sip->sipdomain;
286 /*gboolean new_auth = krb5_auth.gss_context == NULL;
287 if (new_auth) {
288 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
291 if (new_auth || force_reauth) {
292 krb5_token = krb5_auth.base64_token;
295 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
296 krb5_token = krb5_auth.base64_token;*/
298 if (!authuser || strlen(authuser) < 1) {
299 authuser = sip->username;
302 if (auth->type == 1) { /* Digest */
303 sprintf(noncecount, "%08d", auth->nc++);
304 response = purple_cipher_http_digest_calculate_response(
305 "md5", method, target, NULL, NULL,
306 auth->nonce, noncecount, NULL, auth->digest_session_key);
307 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
309 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);
310 g_free(response);
311 return ret;
312 } else if (auth->type == 2) { /* NTLM */
313 // If we have a signature for the message, include that
314 if (msg->signature) {
315 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);
316 return tmp;
319 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
320 const gchar * ntlm_key;
321 const gchar * hostname = purple_get_host_name();
322 gchar * gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
323 auth->ntlm_key = (gchar *)ntlm_key;
324 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
325 g_free(gssapi_data);
326 return tmp;
329 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
330 return tmp;
331 } else if (auth->type == 3) {
332 /* Kerberos */
333 if (auth->nc == 3) {
334 /*if (new_auth || force_reauth) {
335 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
336 if (auth->opaque) {
337 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, krb5_token);
338 } else {
339 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
341 } else {
342 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
343 gchar * mic = "MICTODO";
344 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
345 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
346 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
347 //auth->opaque ? auth->opaque : "", auth->target);
348 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
349 //g_free(mic);
351 return tmp;
353 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
356 sprintf(noncecount, "%08d", auth->nc++);
357 response = purple_cipher_http_digest_calculate_response(
358 "md5", method, target, NULL, NULL,
359 auth->nonce, noncecount, NULL, auth->digest_session_key);
360 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
362 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
363 g_free(response);
364 return ret;
367 static char *parse_attribute(const char *attrname, const char *source)
369 const char *tmp, *tmp2;
370 char *retval = NULL;
371 int len = strlen(attrname);
373 if (!strncmp(source, attrname, len)) {
374 tmp = source + len;
375 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
376 if (tmp2)
377 retval = g_strndup(tmp, tmp2 - tmp);
378 else
379 retval = g_strdup(tmp);
382 return retval;
385 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
387 int i = 0;
388 const char *authuser;
389 char *tmp;
390 gchar **parts;
391 const char *krb5_realm;
392 const char *host;
394 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
395 // and do error checking
397 // KRB realm should always be uppercase
398 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
400 if (sip->realhostname) {
401 host = sip->realhostname;
402 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
403 host = purple_account_get_string(sip->account, "proxy", "");
404 } else {
405 host = sip->sipdomain;
408 authuser = purple_account_get_string(sip->account, "authuser", sip->username);
410 if (!authuser || strlen(authuser) < 1) {
411 authuser = sip->username;
414 if (!hdr) {
415 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
416 return;
419 if (!g_strncasecmp(hdr, "NTLM", 4)) {
420 auth->type = 2;
421 parts = g_strsplit(hdr+5, "\", ", 0);
422 i = 0;
423 while (parts[i]) {
424 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
425 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
426 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
427 g_free(tmp);
429 if ((tmp = parse_attribute("targetname=\"",
430 parts[i]))) {
431 auth->target = tmp;
433 else if ((tmp = parse_attribute("realm=\"",
434 parts[i]))) {
435 auth->realm = tmp;
437 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
438 auth->opaque = tmp;
440 i++;
442 g_strfreev(parts);
443 auth->nc = 1;
444 if (!strstr(hdr, "gssapi-data")) {
445 auth->nc = 1;
446 } else {
447 auth->nc = 3;
449 return;
452 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
453 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
454 auth->type = 3;
455 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
456 parts = g_strsplit(hdr+9, "\", ", 0);
457 i = 0;
458 while (parts[i]) {
459 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
460 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
461 /*if (krb5_auth.gss_context == NULL) {
462 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
464 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
465 g_free(tmp);
467 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
468 auth->target = tmp;
469 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
470 auth->realm = tmp;
471 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
472 auth->opaque = tmp;
474 i++;
476 g_strfreev(parts);
477 auth->nc = 3;
478 return;
481 auth->type = 1;
482 parts = g_strsplit(hdr, " ", 0);
483 while (parts[i]) {
484 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
485 auth->nonce = tmp;
487 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
488 auth->realm = tmp;
490 i++;
492 g_strfreev(parts);
494 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
495 if (auth->realm) {
496 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
497 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
499 auth->nc = 1;
503 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
505 PurpleConnection *gc = data;
506 struct sipe_account_data *sip = gc->proto_data;
507 gsize max_write;
508 gssize written;
510 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
512 if (max_write == 0) {
513 if (sip->tx_handler != 0){
514 purple_input_remove(sip->tx_handler);
515 sip->tx_handler = 0;
517 return;
520 written = write(sip->fd, sip->txbuf->outptr, max_write);
522 if (written < 0 && errno == EAGAIN)
523 written = 0;
524 else if (written <= 0) {
525 /*TODO: do we really want to disconnect on a failure to write?*/
526 purple_connection_error(gc, _("Could not write"));
527 return;
530 purple_circ_buffer_mark_read(sip->txbuf, written);
533 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
535 PurpleConnection *gc = data;
536 struct sipe_account_data *sip = gc->proto_data;
537 gsize max_write;
538 gssize written;
540 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
542 if (max_write == 0) {
543 if (sip->tx_handler != 0) {
544 purple_input_remove(sip->tx_handler);
545 sip->tx_handler = 0;
546 return;
550 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
552 if (written < 0 && errno == EAGAIN)
553 written = 0;
554 else if (written <= 0) {
555 /*TODO: do we really want to disconnect on a failure to write?*/
556 purple_connection_error(gc, _("Could not write"));
557 return;
560 purple_circ_buffer_mark_read(sip->txbuf, written);
563 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
565 static void send_later_cb(gpointer data, gint source, const gchar *error)
567 PurpleConnection *gc = data;
568 struct sipe_account_data *sip;
569 struct sip_connection *conn;
571 if (!PURPLE_CONNECTION_IS_VALID(gc))
573 if (source >= 0)
574 close(source);
575 return;
578 if (source < 0) {
579 purple_connection_error(gc, _("Could not connect"));
580 return;
583 sip = gc->proto_data;
584 sip->fd = source;
585 sip->connecting = FALSE;
586 sip->last_keepalive = time(NULL);
588 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
590 /* If there is more to write now, we need to register a handler */
591 if (sip->txbuf->bufused > 0)
592 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
594 conn = connection_create(sip, source);
595 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
598 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
600 struct sipe_account_data *sip;
601 struct sip_connection *conn;
603 if (!PURPLE_CONNECTION_IS_VALID(gc))
605 if (gsc) purple_ssl_close(gsc);
606 return NULL;
609 sip = gc->proto_data;
610 sip->fd = gsc->fd;
611 sip->gsc = gsc;
612 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
613 sip->connecting = FALSE;
614 sip->last_keepalive = time(NULL);
616 conn = connection_create(sip, gsc->fd);
618 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
620 return sip;
623 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
625 PurpleConnection *gc = data;
626 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
627 if (sip == NULL) return;
629 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
631 /* If there is more to write now */
632 if (sip->txbuf->bufused > 0) {
633 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
638 static void sendlater(PurpleConnection *gc, const char *buf)
640 struct sipe_account_data *sip = gc->proto_data;
642 if (!sip->connecting) {
643 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
644 if (sip->transport == SIPE_TRANSPORT_TLS){
645 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
646 } else {
647 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
648 purple_connection_error(gc, _("Couldn't create socket"));
651 sip->connecting = TRUE;
654 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
655 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
657 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
660 static void sendout_pkt(PurpleConnection *gc, const char *buf)
662 struct sipe_account_data *sip = gc->proto_data;
663 time_t currtime = time(NULL);
664 int writelen = strlen(buf);
666 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
667 if (sip->transport == SIPE_TRANSPORT_UDP) {
668 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
669 purple_debug_info("sipe", "could not send packet\n");
671 } else {
672 int ret;
673 if (sip->fd < 0) {
674 sendlater(gc, buf);
675 return;
678 if (sip->tx_handler) {
679 ret = -1;
680 errno = EAGAIN;
681 } else{
682 if (sip->gsc){
683 ret = purple_ssl_write(sip->gsc, buf, writelen);
684 }else{
685 ret = write(sip->fd, buf, writelen);
689 if (ret < 0 && errno == EAGAIN)
690 ret = 0;
691 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
692 sendlater(gc, buf);
693 return;
696 if (ret < writelen) {
697 if (!sip->tx_handler){
698 if (sip->gsc){
699 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
701 else{
702 sip->tx_handler = purple_input_add(sip->fd,
703 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
704 gc);
708 /* XXX: is it OK to do this? You might get part of a request sent
709 with part of another. */
710 if (sip->txbuf->bufused > 0)
711 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
713 purple_circ_buffer_append(sip->txbuf, buf + ret,
714 writelen - ret);
719 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
721 sendout_pkt(gc, buf);
722 return len;
725 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
727 GSList *tmp = msg->headers;
728 gchar *name;
729 gchar *value;
730 GString *outstr = g_string_new("");
731 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
732 while (tmp) {
733 name = ((struct siphdrelement*) (tmp->data))->name;
734 value = ((struct siphdrelement*) (tmp->data))->value;
735 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
736 tmp = g_slist_next(tmp);
738 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
739 sendout_pkt(sip->gc, outstr->str);
740 g_string_free(outstr, TRUE);
743 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
745 gchar * buf;
746 if (sip->registrar.ntlm_key) {
747 struct sipmsg_breakdown msgbd;
748 msgbd.msg = msg;
749 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
750 // TODO generate this
751 msgbd.rand = g_strdup("0878F41B");
752 sip->registrar.ntlm_num++;
753 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
754 gchar * signature_input_str = sipmsg_breakdown_get_string(&msgbd);
755 if (signature_input_str != NULL) {
756 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
757 msg->rand = g_strdup(msgbd.rand);
758 msg->num = g_strdup(msgbd.num);
760 sipmsg_breakdown_free(&msgbd);
763 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
764 buf = auth_header(sip, &sip->registrar, msg);
765 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
766 sipmsg_add_header(msg, "Authorization", buf);
767 } else {
768 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
770 g_free(buf);
771 } 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")) {
772 sip->registrar.nc=3;
773 sip->registrar.type=2;
775 buf = auth_header(sip, &sip->registrar, msg);
776 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
777 g_free(buf);
778 } else {
779 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
783 static char *get_contact(struct sipe_account_data *sip)
785 return g_strdup(sip->contact);
789 static char *get_contact_service(struct sipe_account_data *sip)
791 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()));
792 //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);
795 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
796 const char *text, const char *body)
798 gchar *name;
799 gchar *value;
800 GString *outstr = g_string_new("");
801 struct sipe_account_data *sip = gc->proto_data;
803 gchar *contact;
804 contact = get_contact(sip);
805 sipmsg_remove_header(msg, "Contact");
806 sipmsg_add_header(msg, "Contact", contact);
807 g_free(contact);
809 /* When sending the acknowlegements and errors, the content length from the original
810 message is still here, but there is no body; we need to make sure we're sending the
811 correct content length */
812 sipmsg_remove_header(msg, "Content-Length");
813 if (body) {
814 gchar len[12];
815 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
816 sipmsg_add_header(msg, "Content-Length", len);
817 } else {
818 sipmsg_add_header(msg, "Content-Length", "0");
821 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
822 //gchar * mic = "MICTODO";
823 msg->response = code;
825 sipmsg_remove_header(msg, "Authentication-Info");
826 sign_outgoing_message(msg, sip, msg->method);
828 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
829 GSList *tmp = msg->headers;
830 while (tmp) {
831 name = ((struct siphdrelement*) (tmp->data))->name;
832 value = ((struct siphdrelement*) (tmp->data))->value;
834 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
835 tmp = g_slist_next(tmp);
837 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
838 sendout_pkt(gc, outstr->str);
839 g_string_free(outstr, TRUE);
842 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
844 if (trans->msg) sipmsg_free(trans->msg);
845 sip->transactions = g_slist_remove(sip->transactions, trans);
846 g_free(trans);
849 static struct transaction *
850 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
852 struct transaction *trans = g_new0(struct transaction, 1);
853 trans->time = time(NULL);
854 trans->msg = (struct sipmsg *)msg;
855 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
856 trans->callback = callback;
857 sip->transactions = g_slist_append(sip->transactions, trans);
858 return trans;
861 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
863 struct transaction *trans;
864 GSList *transactions = sip->transactions;
865 gchar *cseq = sipmsg_find_header(msg, "CSeq");
867 while (transactions) {
868 trans = transactions->data;
869 if (!strcmp(trans->cseq, cseq)) {
870 return trans;
872 transactions = transactions->next;
875 return NULL;
878 static struct transaction *
879 send_sip_request(PurpleConnection *gc, const gchar *method,
880 const gchar *url, const gchar *to, const gchar *addheaders,
881 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
883 struct sipe_account_data *sip = gc->proto_data;
884 const char *addh = "";
885 char *buf;
886 struct sipmsg *msg;
887 gchar *ptmp;
888 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
889 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
890 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
891 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
892 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
893 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
894 gchar *route = strdup("");
896 if (dialog && dialog->routes)
898 GSList *iter = dialog->routes;
900 while(iter)
902 char *tmp = route;
903 route = g_strdup_printf("%sRoute: <%s>\r\n", route, iter->data);
904 g_free(tmp);
905 iter = g_slist_next(iter);
909 if (!ourtag && !dialog) {
910 ourtag = gentag();
913 if (!strcmp(method, "REGISTER")) {
914 if (sip->regcallid) {
915 g_free(callid);
916 callid = g_strdup(sip->regcallid);
917 } else {
918 sip->regcallid = g_strdup(callid);
922 if (addheaders) addh = addheaders;
924 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
925 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
926 "From: <sip:%s>%s%s;epid=%s\r\n"
927 "To: <%s>%s%s%s%s\r\n"
928 "Max-Forwards: 70\r\n"
929 "CSeq: %d %s\r\n"
930 "User-Agent: %s\r\n"
931 "Call-ID: %s\r\n"
932 "%s%s"
933 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
934 method,
935 dialog && dialog->request ? dialog->request : url,
936 TRANSPORT_DESCRIPTOR,
937 purple_network_get_my_ip(-1),
938 sip->listenport,
939 branch ? ";branch=" : "",
940 branch ? branch : "",
941 sip->username,
942 ourtag ? ";tag=" : "",
943 ourtag ? ourtag : "",
944 get_epid(), // TODO generate one per account/login
946 theirtag ? ";tag=" : "",
947 theirtag ? theirtag : "",
948 theirepid ? ";epid=" : "",
949 theirepid ? theirepid : "",
950 dialog ? ++dialog->cseq : ++sip->cseq,
951 method,
952 useragent,
953 callid,
954 route,
955 addh,
956 body ? strlen(body) : 0,
957 body ? body : "");
960 //printf ("parsing msg buf:\n%s\n\n", buf);
961 msg = sipmsg_parse_msg(buf);
963 g_free(buf);
964 g_free(ourtag);
965 g_free(theirtag);
966 g_free(theirepid);
967 g_free(branch);
968 g_free(callid);
969 g_free(route);
971 sign_outgoing_message (msg, sip, method);
973 buf = sipmsg_to_string (msg);
975 /* add to ongoing transactions */
976 struct transaction * trans = transactions_add_buf(sip, msg, tc);
977 sendout_pkt(gc, buf);
979 return trans;
982 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
984 gchar *from = g_strdup_printf("sip:%s", sip->username);
985 gchar *contact = get_contact(sip);
986 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
987 "Content-Type: application/SOAP+xml\r\n",contact);
989 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
990 tr->payload = payload;
992 g_free(from);
993 g_free(hdr);
996 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
998 send_soap_request_with_cb(sip, body, NULL, NULL);
1001 static char *get_contact_register(struct sipe_account_data *sip)
1003 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()));
1006 static void do_register_exp(struct sipe_account_data *sip, int expire)
1008 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1009 char *to = g_strdup_printf("sip:%s", sip->username);
1010 char *contact = get_contact_register(sip);
1011 char *hdr = g_strdup_printf("Contact: %s\r\n"
1012 "Supported: gruu-10, adhoclist\r\n"
1013 "Event: registration\r\n"
1014 "Allow-Events: presence\r\n"
1015 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1016 "Expires: %d\r\n", contact,expire);
1017 g_free(contact);
1019 sip->registerstatus = 1;
1021 if (expire) {
1022 sip->reregister = time(NULL) + expire - 50;
1023 } else {
1024 sip->reregister = time(NULL) + 600;
1027 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1028 process_register_response);
1030 g_free(hdr);
1031 g_free(uri);
1032 g_free(to);
1035 static void do_register(struct sipe_account_data *sip)
1037 do_register_exp(sip, sip->registerexpire);
1040 static gchar *parse_from(const gchar *hdr)
1042 gchar *from;
1043 const gchar *tmp, *tmp2 = hdr;
1045 if (!hdr) return NULL;
1046 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1047 tmp = strchr(hdr, '<');
1049 /* i hate the different SIP UA behaviours... */
1050 if (tmp) { /* sip address in <...> */
1051 tmp2 = tmp + 1;
1052 tmp = strchr(tmp2, '>');
1053 if (tmp) {
1054 from = g_strndup(tmp2, tmp - tmp2);
1055 } else {
1056 purple_debug_info("sipe", "found < without > in From\n");
1057 return NULL;
1059 } else {
1060 tmp = strchr(tmp2, ';');
1061 if (tmp) {
1062 from = g_strndup(tmp2, tmp - tmp2);
1063 } else {
1064 from = g_strdup(tmp2);
1067 purple_debug_info("sipe", "got %s\n", from);
1068 return from;
1071 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1073 va_list args;
1074 xmlnode * node;
1075 const gchar * name;
1077 va_start(args, parent);
1078 while ((name = va_arg(args, const char *)) != NULL) {
1079 node = xmlnode_get_child(parent, name);
1080 if (node == NULL) return NULL;
1081 parent = node;
1083 va_end(args);
1085 return node;
1088 static void
1089 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1091 PurpleGroup * purple_group = purple_find_group(group->name);
1092 if (!purple_group) {
1093 purple_group = purple_group_new(group->name);
1094 purple_blist_add_group(purple_group, NULL);
1097 if (purple_group) {
1098 group->purple_group = purple_group;
1099 sip->groups = g_slist_append(sip->groups, group);
1100 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1101 } else {
1102 purple_debug_info("sipe", "did not add group %s\n", group->name);
1106 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1108 if (sip == NULL) {
1109 return NULL;
1112 struct sipe_group *group;
1113 GSList *entry = sip->groups;
1114 while (entry) {
1115 group = entry->data;
1116 if (group->id == id) {
1117 return group;
1119 entry = entry->next;
1121 return NULL;
1124 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1126 if (sip == NULL) {
1127 return NULL;
1130 struct sipe_group *group;
1131 GSList *entry = sip->groups;
1132 while (entry) {
1133 group = entry->data;
1134 if (!strcmp(group->name, name)) {
1135 return group;
1137 entry = entry->next;
1139 return NULL;
1142 static void
1143 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1145 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1146 gchar * body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->delta_num++);
1147 send_soap_request(sip, body);
1148 g_free(body);
1149 g_free(group->name);
1150 group->name = g_strdup(name);
1153 static void
1154 sipe_group_set_user (struct sipe_account_data *sip, struct sipe_group * group, const gchar * who)
1156 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1157 PurpleBuddy * purple_buddy = purple_find_buddy (sip->account, who);
1159 if (!group) {
1160 group = sipe_group_find_by_id (sip, buddy->group_id);
1162 buddy->group_id = group ? group->id : 1;
1164 if (buddy && purple_buddy) {
1165 gchar * alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1166 purple_debug_info("sipe", "Saving buddy %s with alias %s and group_id %d\n", who, alias, buddy->group_id);
1167 gchar * body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1168 alias, buddy->group_id, "true", buddy->name, sip->delta_num++
1170 send_soap_request(sip, body);
1171 g_free(body);
1175 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1177 if (msg->response == 200) {
1178 struct sipe_group * group = g_new0(struct sipe_group, 1);
1180 struct group_user_context * ctx = (struct group_user_context*)tc->payload;
1181 group->name = ctx->group_name;
1183 xmlnode * xml = xmlnode_from_str(msg->body, msg->bodylen);
1184 if (!xml) return FALSE;
1186 xmlnode * node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1187 if (!node) return FALSE;
1189 char * group_id = xmlnode_get_data(node);
1190 if (!group_id) return FALSE;
1192 group->id = (int)g_ascii_strtod(group_id, NULL);
1194 sipe_group_add(sip, group);
1195 sipe_group_set_user(sip, group, ctx->user_name);
1197 g_free(ctx);
1198 xmlnode_free(xml);
1199 return TRUE;
1201 return FALSE;
1204 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1206 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1207 ctx->group_name = g_strdup(name);
1208 ctx->user_name = g_strdup(who);
1210 gchar * body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->delta_num++);
1211 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1212 g_free(body);
1215 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1217 gchar *to;
1219 if (msg->response == 200 || msg->response == 202) {
1220 return TRUE;
1223 to = parse_from(sipmsg_find_header(tc->msg, "To")); /* cant be NULL since it is our own msg */
1225 /* we can not subscribe -> user is offline (TODO unknown status?) */
1227 purple_prpl_got_user_status(sip->account, to, "offline", NULL);
1228 g_free(to);
1229 return TRUE;
1232 static void sipe_subscribe_to_name(struct sipe_account_data *sip, const char * buddy_name)
1234 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1235 gchar *tmp = get_contact(sip);
1236 gchar *contact = g_strdup_printf(
1237 "Accept: application/pidf+xml, application/xpidf+xml\r\n"
1238 "Event: presence\r\n"
1239 "Contact: %s\r\n", tmp);
1240 g_free(tmp);
1242 /* subscribe to buddy presence */
1243 send_sip_request(sip->gc, "SUBSCRIBE", to, to, contact, "", NULL, process_subscribe_response);
1245 g_free(to);
1246 g_free(contact);
1249 static void sipe_subscribe(struct sipe_account_data *sip, struct sipe_buddy *buddy)
1251 sipe_subscribe_to_name(sip, buddy->name);
1253 /* resubscribe before subscription expires */
1254 /* add some jitter */
1255 buddy->resubscribe = time(NULL)+1140+(rand()%50);
1258 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1260 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(purple_status_get_type(status));
1261 struct sipe_account_data *sip = NULL;
1263 if (!purple_status_is_active(status))
1264 return;
1266 if (account->gc)
1267 sip = account->gc->proto_data;
1269 if (sip) {
1270 g_free(sip->status);
1272 if (primitive == PURPLE_STATUS_AWAY) {
1273 sip->status = g_strdup("away");
1274 } else if (primitive == PURPLE_STATUS_AVAILABLE) {
1275 sip->status = g_strdup("available");
1276 } else if (primitive == PURPLE_STATUS_UNAVAILABLE) {
1277 sip->status = g_strdup("busy");
1280 send_presence_info(sip);
1284 static void
1285 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1287 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1288 sipe_group_set_user(sip, NULL, name);
1291 static void
1292 sipe_group_buddy(PurpleConnection *gc,
1293 const char *who,
1294 const char *old_group_name,
1295 const char *new_group_name)
1297 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1298 struct sipe_group * group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1299 if (!group) {
1300 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1301 } else {
1302 sipe_group_set_user(sip, group, who);
1306 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1308 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1309 struct sipe_buddy *b;
1311 // Prepend sip: if needed
1312 if (strncmp("sip:", buddy->name, 4)) {
1313 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1314 purple_blist_rename_buddy(buddy, buf);
1315 g_free(buf);
1318 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1319 b = g_new0(struct sipe_buddy, 1);
1320 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1321 b->name = g_strdup(buddy->name);
1322 g_hash_table_insert(sip->buddies, b->name, b);
1324 sipe_group_buddy(gc, b->name, NULL, group->name);
1325 } else {
1326 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1330 // Not Used; Remove?
1331 /*static void sipe_get_buddies(PurpleConnection *gc)
1333 PurpleBlistNode *gnode, *cnode, *bnode;
1335 purple_debug_info("sipe", "sipe_get_buddies\n");
1337 for (gnode = purple_get_blist()->root; gnode; gnode = gnode->next) {
1338 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) continue;
1339 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1340 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) continue;
1341 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1342 if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) continue;
1343 if (((PurpleBuddy*)bnode)->account == gc->account)
1344 sipe_add_buddy(gc, (PurpleBuddy*)bnode, (PurpleGroup *)gnode);
1350 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1352 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1353 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1355 if (!b) return;
1356 g_hash_table_remove(sip->buddies, buddy->name);
1358 if (b->name) {
1359 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->delta_num++);
1360 send_soap_request(sip, body);
1361 g_free(body);
1364 g_free(b->name);
1365 g_free(b);
1368 static void
1369 sipe_rename_group(PurpleConnection *gc,
1370 const char *old_name,
1371 PurpleGroup *group,
1372 GList *moved_buddies)
1374 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1375 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1376 if (group) {
1377 sipe_group_rename(sip, s_group, group->name);
1378 } else {
1379 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1383 static void
1384 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1386 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1387 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1388 if (s_group) {
1389 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1390 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->delta_num++);
1391 send_soap_request(sip, body);
1392 g_free(body);
1394 sip->groups = g_slist_remove(sip->groups, s_group);
1395 g_free(s_group->name);
1396 } else {
1397 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1401 static GList *sipe_status_types(PurpleAccount *acc)
1403 PurpleStatusType *type;
1404 GList *types = NULL;
1406 // Available
1407 type = purple_status_type_new_with_attrs(
1408 PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
1409 // Translators: noun
1410 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1411 NULL);
1412 types = g_list_append(types, type);
1414 // Away
1415 type = purple_status_type_new_with_attrs(
1416 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1417 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1418 NULL);
1419 types = g_list_append(types, type);
1421 // Busy
1422 type = purple_status_type_new_with_attrs(
1423 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1424 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1425 NULL);
1426 types = g_list_append(types, type);
1428 // Offline
1429 type = purple_status_type_new_full(
1430 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1431 types = g_list_append(types, type);
1433 return types;
1436 static gboolean sipe_add_lcs_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1438 int len = msg->bodylen;
1440 gchar *tmp = sipmsg_find_header(msg, "Event");
1441 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1442 return FALSE;
1445 /* Convert the contact from XML to Purple Buddies */
1446 xmlnode * isc = xmlnode_from_str(msg->body, len);
1447 if (!isc) {
1448 return FALSE;
1451 gchar * delta_num = g_strdup(xmlnode_get_attrib(isc, "deltaNum"));
1452 if (delta_num) {
1453 sip->delta_num = (int)g_ascii_strtod(delta_num, NULL);
1456 /* Parse groups */
1457 xmlnode *group_node;
1458 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1459 struct sipe_group * group = g_new0(struct sipe_group, 1);
1461 group->name = g_strdup(xmlnode_get_attrib(group_node, "name"));
1462 if (!strncmp(group->name, "~", 1)){
1463 // TODO translate
1464 group->name = "General";
1466 group->name = g_strdup(group->name);
1467 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1469 sipe_group_add(sip, group);
1472 // Make sure we have at least one group
1473 if (g_slist_length(sip->groups) == 0) {
1474 struct sipe_group * group = g_new0(struct sipe_group, 1);
1475 // TODO translate
1476 group->name = g_strdup("General");
1477 group->id = 1;
1478 PurpleGroup * purple_group = purple_group_new(group->name);
1479 purple_blist_add_group(purple_group, NULL);
1480 sip->groups = g_slist_append(sip->groups, group);
1483 /* Parse contacts */
1484 xmlnode *item;
1485 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1486 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1487 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1488 gchar **item_groups = g_strsplit(xmlnode_get_attrib(item, "groups"), " ", 0);
1490 struct sipe_group * group = NULL;
1492 // Find the first group this contact belongs to; that's where we'll place it in the buddy list
1493 if (item_groups[0]) {
1494 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[0], NULL));
1497 // If couldn't find the right group for this contact, just put them in the first group we have
1498 if (group == NULL && g_slist_length(sip->groups) > 0) {
1499 group = sip->groups->data;
1502 if (group != NULL) {
1503 char * buddy_name = g_strdup_printf("sip:%s", uri);
1505 //b = purple_find_buddy(sip->account, buddy_name);
1506 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1507 if (!b){
1508 b = purple_buddy_new(sip->account, buddy_name, uri);
1510 g_free(buddy_name);
1512 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1514 if (name != NULL && strlen(name) != 0) {
1515 purple_blist_alias_buddy(b, name);
1516 } else {
1517 purple_blist_alias_buddy(b, uri);
1520 struct sipe_buddy * buddy = g_new0(struct sipe_buddy, 1);
1521 buddy->name = g_strdup(b->name);
1522 buddy->group_id = group->id;
1523 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1525 purple_debug_info("sipe", "Added buddy %s to group %s\n", buddy->name, group->name);
1526 } else {
1527 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1528 name);
1532 xmlnode_free(isc);
1534 return 0;
1537 static void sipe_subscribe_buddylist(struct sipe_account_data *sip,struct sipmsg *msg)
1539 gchar *to = g_strdup_printf("sip:%s", sip->username);
1540 gchar *tmp = get_contact(sip);
1541 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-roaming-contacts\r\n"
1542 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
1543 "Supported: com.microsoft.autoextend\r\n"
1544 "Supported: ms-benotify\r\n"
1545 "Proxy-Require: ms-benotify\r\n"
1546 "Supported: ms-piggyback-first-notify\r\n"
1547 "Contact: %s\r\n", tmp);
1548 g_free(tmp);
1550 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_add_lcs_contacts);
1551 g_free(to);
1552 g_free(hdr);
1555 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
1557 gchar *to = g_strdup_printf("sip:%s", sip->username);
1558 gchar *tmp = get_contact(sip);
1559 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-roaming-self\r\n"
1560 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
1561 "Supported: com.microsoft.autoextend\r\n"
1562 "Supported: ms-benotify\r\n"
1563 "Proxy-Require: ms-benotify\r\n"
1564 "Supported: ms-piggyback-first-notify\r\n"
1565 "Contact: %s\r\n"
1566 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n"
1567 ,tmp);
1569 g_free(tmp);
1571 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>");
1573 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
1574 g_free(body);
1575 g_free(to);
1576 g_free(hdr);
1579 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
1581 gchar *to = g_strdup_printf("sip:%s", sip->username);
1582 gchar *tmp = get_contact(sip);
1583 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-provisioning-v2\r\n"
1584 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
1585 "Supported: com.microsoft.autoextend\r\n"
1586 "Supported: ms-benotify\r\n"
1587 "Proxy-Require: ms-benotify\r\n"
1588 "Supported: ms-piggyback-first-notify\r\n"
1589 "Expires: 0\r\n"
1590 "Contact: %s\r\n"
1591 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
1592 ,tmp);
1594 g_free(tmp);
1596 gchar *body=g_strdup("<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\"><provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/><provisioningGroup name=\"ucPolicy\"/></provisioningGroupList>");
1597 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
1598 g_free(body);
1599 g_free(to);
1600 g_free(hdr);
1603 /* IM Session (INVITE and MESSAGE methods) */
1605 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
1607 if (sip == NULL || who == NULL) {
1608 return NULL;
1611 struct sip_im_session *session;
1612 GSList *entry = sip->im_sessions;
1613 while (entry) {
1614 session = entry->data;
1615 if ((who != NULL && !strcmp(who, session->with))) {
1616 return session;
1618 entry = entry->next;
1620 return NULL;
1623 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
1625 struct sip_im_session *session = find_im_session(sip, who);
1626 if (!session) {
1627 session = g_new0(struct sip_im_session, 1);
1628 session->with = g_strdup(who);
1629 sip->im_sessions = g_slist_append(sip->im_sessions, session);
1631 return session;
1634 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
1636 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
1637 // TODO free session resources
1640 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
1642 gchar *hdr;
1643 gchar *fullto;
1644 gchar *tmp;
1646 if (strncmp("sip:", session->with, 4)) {
1647 fullto = g_strdup_printf("sip:%s", session->with);
1648 } else {
1649 fullto = g_strdup(session->with);
1652 hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
1653 //hdr = g_strdup("Content-Type: text/rtf\r\n");
1654 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
1656 tmp = get_contact(sip);
1657 hdr = g_strdup_printf("Contact: %s\r\n%s", tmp, hdr);
1658 g_free(tmp);
1660 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msg, session->dialog, NULL);
1662 g_free(hdr);
1663 g_free(fullto);
1667 static void
1668 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
1670 GSList *entry = session->outgoing_message_queue;
1671 while (entry) {
1672 char *queued_msg = entry->data;
1673 sipe_send_message(sip, session, queued_msg);
1675 // Remove from the queue and free the string
1676 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
1677 g_free(queued_msg);
1681 static void
1682 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
1684 GSList *hdr = msg->headers;
1685 struct siphdrelement *elem;
1686 gchar *contact;
1688 while(hdr)
1690 elem = hdr->data;
1691 if(!strcmp(elem->name, "Record-Route"))
1693 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
1694 dialog->routes = g_slist_append(dialog->routes, route);
1696 hdr = g_slist_next(hdr);
1699 if (outgoing)
1701 dialog->routes = g_slist_reverse(dialog->routes);
1704 if (dialog->routes)
1706 dialog->request = dialog->routes->data;
1707 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
1710 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
1711 dialog->routes = g_slist_append(dialog->routes, contact);
1715 static void
1716 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
1718 gchar *us = outgoing ? "From" : "To";
1719 gchar *them = outgoing ? "To" : "From";
1721 dialog->callid = sipmsg_find_header(msg, "Call-ID");
1722 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
1723 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
1724 if (!dialog->theirepid) {
1725 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
1727 if (!dialog->theirepid) {
1728 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
1731 sipe_get_route_header(msg, dialog, outgoing);
1735 static gboolean
1736 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1738 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
1739 struct sip_im_session * session = find_im_session(sip, with);
1740 g_free(with);
1742 if (!session) {
1743 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
1744 return FALSE;
1747 if (msg->response != 200) {
1748 purple_debug_info("sipe", "process_invite_response: INVITE response not 200, ignoring\n");
1749 im_session_destroy(sip, session);
1750 return FALSE;
1753 struct sip_dialog * dialog = session->dialog;
1754 if (!dialog) {
1755 purple_debug_info("sipe", "process_invite_response: session outgoign dialog is NULL\n");
1756 return FALSE;
1759 sipe_parse_dialog(msg, dialog, TRUE);
1760 dialog->cseq = 0;
1762 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
1763 session->outgoing_invite = NULL;
1764 sipe_im_process_queue(sip, session);
1766 return TRUE;
1770 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
1772 gchar *hdr;
1773 gchar *to;
1774 gchar *contact;
1775 gchar *body;
1777 if (session->dialog) {
1778 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
1779 return;
1782 session->dialog = g_new0(struct sip_dialog, 1);
1784 if (strstr(session->with, "sip:")) {
1785 to = g_strdup(session->with);
1786 } else {
1787 to = g_strdup_printf("sip:%s", session->with);
1790 char * base64_msg = purple_base64_encode((guchar*) msg_body, strlen(msg_body));
1791 char * ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, base64_msg);
1792 g_free(base64_msg);
1794 contact = get_contact(sip);
1795 hdr = g_strdup_printf(
1796 "Contact: %s\r\n%s"
1797 "Content-Type: application/sdp\r\n",
1798 contact, ms_text_format, sip->username, sip->username, to);
1799 g_free(ms_text_format);
1801 body = g_strdup_printf(
1802 "v=0\r\n"
1803 "o=- 0 0 IN IP4 %s\r\n"
1804 "s=session\r\n"
1805 "c=IN IP4 %s\r\n"
1806 "t=0 0\r\n"
1807 "m=message %d sip null\r\n"
1808 "a=accept-types:text/plain text/html image/gif "
1809 "multipart/alternative application/im-iscomposing+xml\r\n",
1810 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
1812 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
1813 to, to, hdr, body, session->dialog, process_invite_response);
1815 g_free(to);
1816 g_free(body);
1817 g_free(hdr);
1818 g_free(contact);
1821 static void
1822 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
1824 if (session) {
1825 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
1826 im_session_destroy(sip, session);
1830 static void
1831 sipe_convo_closed(PurpleConnection * gc, const char *who)
1833 struct sipe_account_data *sip = gc->proto_data;
1835 purple_debug_info("sipe", "conversation with %s closed\n", who);
1836 im_session_close(sip, find_im_session(sip, who));
1839 static void
1840 im_session_close_all (struct sipe_account_data *sip)
1842 GSList *entry = sip->im_sessions;
1843 while (entry) {
1844 im_session_close (sip, entry->data);
1845 entry = sip->im_sessions;
1849 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
1851 struct sipe_account_data *sip = gc->proto_data;
1852 char *to = g_strdup(who);
1853 char *text = purple_unescape_html(what);
1855 struct sip_im_session * session = find_or_create_im_session(sip, who);
1857 // Queue the message
1858 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
1860 if (session->dialog && session->dialog->callid) {
1861 sipe_im_process_queue(sip, session);
1862 } else if (!session->outgoing_invite) {
1863 // Need to send the INVITE to get the outgoing dialog setup
1864 sipe_invite(sip, session, text);
1867 g_free(to);
1868 return 1;
1872 /* End IM Session (INVITE and MESSAGE methods) */
1874 static unsigned int
1875 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
1877 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1879 if (state == PURPLE_NOT_TYPING)
1880 return 0;
1882 struct sip_im_session * session = find_im_session(sip, who);
1884 if (session && session->dialog) {
1885 send_sip_request(gc, "INFO", who, who,
1886 "Content-Type: application/xml\r\n",
1887 SIPE_SEND_TYPING, session->dialog, NULL);
1890 return SIPE_TYPING_SEND_TIMEOUT;
1894 static void sipe_buddy_resub(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1896 time_t curtime = time(NULL);
1897 if (buddy->resubscribe < curtime) {
1898 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_buddy_resub %s\n", name);
1899 sipe_subscribe(sip, buddy);
1903 static gboolean resend_timeout(struct sipe_account_data *sip)
1905 GSList *tmp = sip->transactions;
1906 time_t currtime = time(NULL);
1907 while (tmp) {
1908 struct transaction *trans = tmp->data;
1909 tmp = tmp->next;
1910 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
1911 if ((currtime - trans->time > 5) && trans->retries >= 1) {
1912 /* TODO 408 */
1913 } else {
1914 if ((currtime - trans->time > 2) && trans->retries == 0) {
1915 trans->retries++;
1916 sendout_sipmsg(sip, trans->msg);
1920 return TRUE;
1923 static gboolean subscribe_timeout(struct sipe_account_data *sip)
1925 GSList *tmp;
1926 time_t curtime = time(NULL);
1927 /* register again if first registration expires */
1928 if (sip->reregister < curtime) {
1929 do_register(sip);
1931 /* check for every subscription if we need to resubscribe */
1932 //Fixxxer we need resub?
1933 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_resub, (gpointer)sip);
1935 /* remove a timed out suscriber */
1937 tmp = sip->watcher;
1938 while (tmp) {
1939 struct sipe_watcher *watcher = tmp->data;
1940 if (watcher->expire < curtime) {
1941 watcher_remove(sip, watcher->name);
1942 tmp = sip->watcher;
1944 if (tmp) tmp = tmp->next;
1947 return TRUE;
1950 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
1952 gchar *from;
1953 gchar *contenttype;
1954 gboolean found = FALSE;
1956 from = parse_from(sipmsg_find_header(msg, "From"));
1958 if (!from) return;
1960 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
1962 contenttype = sipmsg_find_header(msg, "Content-Type");
1963 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
1964 serv_got_im(sip->gc, from, msg->body, 0, time(NULL));
1965 send_sip_response(sip->gc, msg, 200, "OK", NULL);
1966 found = TRUE;
1968 if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
1969 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
1970 xmlnode *state;
1971 gchar *statedata;
1973 if (!isc) {
1974 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
1975 return;
1978 state = xmlnode_get_child(isc, "state");
1980 if (!state) {
1981 purple_debug_info("sipe", "process_incoming_message: no state found\n");
1982 xmlnode_free(isc);
1983 return;
1986 statedata = xmlnode_get_data(state);
1987 if (statedata) {
1988 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
1989 else serv_got_typing_stopped(sip->gc, from);
1991 g_free(statedata);
1993 xmlnode_free(isc);
1994 send_sip_response(sip->gc, msg, 200, "OK", NULL);
1995 found = TRUE;
1997 if (!found) {
1998 purple_debug_info("sipe", "got unknown mime-type");
1999 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2001 g_free(from);
2004 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2006 // Only accept text invitations
2007 if (msg->body && !strstr(msg->body, "m=message")) {
2008 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2009 return;
2012 gchar * from = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "<", ">", NULL);
2013 struct sip_im_session * session = find_or_create_im_session (sip, from);
2014 if (session) {
2015 if (session->dialog) {
2016 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2017 } else {
2018 session->dialog = g_new0(struct sip_dialog, 1);
2020 sipe_parse_dialog(msg, session->dialog, FALSE);
2022 session->dialog->callid = sipmsg_find_header(msg, "Call-ID");
2023 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2024 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2025 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2027 } else {
2028 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2030 g_free(from);
2032 send_sip_response(sip->gc, msg, 200, "OK", g_strdup_printf(
2033 "v=0\r\n"
2034 "o=- 0 0 IN IP4 %s\r\n"
2035 "s=session\r\n"
2036 "c=IN IP4 %s\r\n"
2037 "t=0 0\r\n"
2038 "m=message %d sip sip:%s\r\n"
2039 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2040 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2041 sip->realport, sip->username));
2044 static void sipe_connection_cleanup(struct sipe_account_data *);
2045 static void create_connection(struct sipe_account_data *, gchar *, int);
2047 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2049 gchar *tmp, krb5_token;
2050 const gchar *expires_header;
2051 int expires;
2053 expires_header = sipmsg_find_header(msg, "Expires");
2054 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2055 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2057 switch (msg->response) {
2058 case 200:
2059 if (expires == 0) {
2060 sip->registerstatus = 0;
2061 } else {
2062 sip->registerstatus = 3;
2063 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
2065 gchar *gruu = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "gruu=\"", "\"", NULL);
2066 if(gruu) {
2067 sip->contact = g_strdup_printf("<%s>", gruu);
2068 } else {
2069 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);
2072 /* get buddies from blist; Has a bug */
2073 subscribe_timeout(sip);
2075 tmp = sipmsg_find_header(msg, "Allow-Events");
2076 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
2077 sipe_subscribe_buddylist(sip, msg);
2080 tmp = sipmsg_find_header(msg, "ms-keep-alive");
2081 if (tmp) {
2082 sipe_keep_alive_timeout(sip, tmp);
2085 sipe_subscribe_roaming_self(sip, msg);
2086 sipe_subscribe_roaming_provisioning(sip, msg);
2087 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
2089 // Should we remove the transaction here?
2090 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
2091 transactions_remove(sip, tc);
2093 break;
2094 case 301:
2096 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
2098 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
2099 gchar **parts = g_strsplit(redirect + 4, ";", 0);
2100 gchar **tmp;
2101 gchar *hostname;
2102 int port = 0;
2103 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
2104 int i = 1;
2106 tmp = g_strsplit(parts[0], ":", 0);
2107 hostname = g_strdup(tmp[0]);
2108 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
2109 g_strfreev(tmp);
2111 while (parts[i]) {
2112 tmp = g_strsplit(parts[i], "=", 0);
2113 if (tmp[1]) {
2114 if (g_strcasecmp("transport", tmp[0]) == 0) {
2115 if (g_strcasecmp("tcp", tmp[1]) == 0) {
2116 transport = SIPE_TRANSPORT_TCP;
2117 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
2118 transport = SIPE_TRANSPORT_UDP;
2122 g_strfreev(tmp);
2123 i++;
2125 g_strfreev(parts);
2127 /* Close old connection */
2128 sipe_connection_cleanup(sip);
2130 /* Create new connection */
2131 sip->transport = transport;
2132 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
2133 hostname, port, TRANSPORT_DESCRIPTOR);
2134 create_connection(sip, hostname, port);
2137 break;
2138 case 401:
2139 if (sip->registerstatus != 2) {
2140 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
2141 if (sip->registrar.retries > 3) {
2142 sip->gc->wants_to_die = TRUE;
2143 purple_connection_error(sip->gc, _("Wrong Password"));
2144 return TRUE;
2146 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
2147 tmp = sipmsg_find_auth_header(msg, "NTLM");
2148 } else {
2149 tmp = sipmsg_find_auth_header(msg, "Kerberos");
2151 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
2152 fill_auth(sip, tmp, &sip->registrar);
2153 sip->registerstatus = 2;
2154 if (sip->account->disconnecting) {
2155 do_register_exp(sip, 0);
2156 } else {
2157 do_register(sip);
2160 break;
2161 case 403:
2163 const gchar *warning = sipmsg_find_header(msg, "Warning");
2164 if (warning != NULL) {
2165 /* Example header:
2166 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
2168 gchar **tmp = g_strsplit(warning, "\"", 0);
2169 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
2170 g_strfreev(tmp);
2171 } else {
2172 warning = _("You have been rejected by the server");
2175 sip->gc->wants_to_die = TRUE;
2176 purple_connection_error(sip->gc, warning);
2177 return TRUE;
2179 break;
2180 case 404:
2182 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2183 if (warning != NULL) {
2184 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2185 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
2186 g_free(reason);
2187 } else {
2188 warning = _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
2191 sip->gc->wants_to_die = TRUE;
2192 purple_connection_error(sip->gc, warning);
2193 return TRUE;
2195 break;
2196 case 503:
2198 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2199 if (warning != NULL) {
2200 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2201 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
2202 g_free(reason);
2203 } else {
2204 warning = _("Service unavailable: no reason given");
2207 sip->gc->wants_to_die = TRUE;
2208 purple_connection_error(sip->gc, warning);
2209 return TRUE;
2211 break;
2213 return TRUE;
2216 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2218 gchar *from;
2219 gchar *fromhdr;
2220 gchar *tmp2;
2221 gchar *activity = g_strdup("available");
2222 xmlnode *pidf;
2223 xmlnode *basicstatus = NULL, *tuple, *status;
2224 gboolean isonline = FALSE;
2226 fromhdr = sipmsg_find_header(msg, "From");
2227 from = parse_from(fromhdr);
2229 if (!from) {
2230 return;
2233 pidf = xmlnode_from_str(msg->body, msg->bodylen);
2234 if (!pidf) {
2235 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf\n");
2236 return;
2239 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
2240 if ((status = xmlnode_get_child(tuple, "status"))) {
2241 basicstatus = xmlnode_get_child(status, "basic");
2245 if (!basicstatus) {
2246 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
2247 xmlnode_free(pidf);
2248 return;
2251 tmp2 = xmlnode_get_data(basicstatus);
2252 if (!tmp2) {
2253 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
2254 xmlnode_free(pidf);
2255 return;
2258 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", tmp2);
2259 if (strstr(tmp2, "open")) {
2260 isonline = TRUE;
2263 xmlnode *display_name_node = xmlnode_get_child(pidf, "display-name");
2264 if (display_name_node) {
2265 PurpleBuddy * buddy = purple_find_buddy (sip->account, from);
2266 char * display_name = xmlnode_get_data(display_name_node);
2267 if (buddy && display_name) {
2268 purple_blist_server_alias_buddy (buddy, g_strdup(display_name));
2272 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
2273 if ((status = xmlnode_get_child(tuple, "status"))) {
2274 if (basicstatus = xmlnode_get_child(status, "activities")) {
2275 if (basicstatus = xmlnode_get_child(basicstatus, "activity")) {
2276 activity = xmlnode_get_data(basicstatus);
2281 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
2283 if (isonline) {
2284 gchar * status_id = NULL;
2285 if (activity) {
2286 if (strstr(activity, "busy")) {
2287 status_id = "busy";
2288 } else if (strstr(activity, "away")) {
2289 status_id = "away";
2293 if (!status_id) {
2294 status_id = "available";
2297 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
2298 purple_prpl_got_user_status(sip->account, from, status_id, NULL);
2299 } else {
2300 purple_prpl_got_user_status(sip->account, from, "offline", NULL);
2303 xmlnode_free(pidf);
2304 g_free(from);
2305 g_free(tmp2);
2306 g_free(activity);
2308 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2311 static void process_incoming_benotify(struct sipe_account_data *sip, struct sipmsg *msg)
2313 xmlnode *xml = xmlnode_from_str(msg->body, msg->bodylen);
2314 if (!xml) return;
2316 gchar * delta_num = g_strdup(xmlnode_get_attrib(xml, "deltaNum"));
2317 if (delta_num) {
2318 sip->delta_num = (int)g_ascii_strtod(delta_num, NULL);
2321 xmlnode_free(xml);
2325 static gchar* gen_xpidf(struct sipe_account_data *sip)
2327 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
2328 "<presence>\r\n"
2329 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
2330 "<display name=\"sip:%s\"/>\r\n"
2331 "<atom id=\"1234\">\r\n"
2332 "<address uri=\"sip:%s\">\r\n"
2333 "<status status=\"%s\"/>\r\n"
2334 "</address>\r\n"
2335 "</atom>\r\n"
2336 "</presence>\r\n",
2337 sip->username,
2338 sip->username,
2339 sip->username,
2340 sip->status);
2341 return doc;
2346 static gchar* gen_pidf(struct sipe_account_data *sip)
2348 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
2349 "<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"
2350 "<tuple id=\"0\">\r\n"
2351 "<status>\r\n"
2352 "<basic>open</basic>\r\n"
2353 "<ep:activities>\r\n"
2354 " <ep:activity>%s</ep:activity>\r\n"
2355 "</ep:activities>"
2356 "</status>\r\n"
2357 "</tuple>\r\n"
2358 "<ci:display-name>%s</ci:display-name>\r\n"
2359 "</presence>",
2360 sip->username,
2361 sip->status,
2362 sip->username);
2363 return doc;
2366 static void send_clear_notes(struct sipe_account_data *sip)
2370 static gboolean
2371 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2373 if (msg->response == 488) {
2374 sip->presence_method_version = 1;
2375 send_presence_info(sip);
2377 return TRUE;
2380 static void send_presence_info_v0(struct sipe_account_data *sip, char * note)
2382 int code;
2383 if (!strcmp(sip->status, "away")) {
2384 code = 100;
2385 } else if (!strcmp(sip->status, "busy")) {
2386 code = 600;
2387 } else {
2388 // Available
2389 code = 400;
2392 gchar *name = g_strdup_printf("sip: sip:%s", sip->username);
2393 gchar * body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, 200, code, note);
2394 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
2395 g_free(name);
2396 g_free(body);
2399 static gboolean
2400 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2402 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
2403 if (msg->response == 200) {
2404 sip->status_version = 0;
2405 send_presence_info(sip);
2407 return TRUE;
2410 static gboolean
2411 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2413 if (msg->response == 409) {
2414 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
2415 // TODO need to parse the version #'s?
2416 gchar *uri = g_strdup_printf("sip:%s", sip->username);
2417 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
2419 gchar *tmp = get_contact(sip);
2420 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
2421 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
2423 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
2425 g_free(tmp);
2426 g_free(hdr);
2427 g_free(uri);
2428 g_free(doc);
2430 return TRUE;
2433 static void send_presence_info_v1(struct sipe_account_data *sip, char * note)
2435 int code;
2436 if (!strcmp(sip->status, "away")) {
2437 code = 12000;
2438 } else if (!strcmp(sip->status, "busy")) {
2439 code = 6000;
2440 } else {
2441 // Available
2442 code = 3000;
2445 gchar *uri = g_strdup_printf("sip:%s", sip->username);
2446 gchar *doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
2447 sip->status_version, code,
2448 sip->status_version, code,
2449 sip->status_version, note ? note : "",
2450 sip->status_version, note ? note : "",
2451 sip->status_version, note ? note : ""
2453 sip->status_version++;
2455 gchar *tmp = get_contact(sip);
2456 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
2457 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
2459 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
2461 g_free(tmp);
2462 g_free(hdr);
2463 g_free(uri);
2464 g_free(doc);
2467 static void send_presence_info(struct sipe_account_data *sip)
2469 PurpleStatus * status = purple_account_get_active_status(sip->account);
2470 if (!status) return;
2472 gchar *note = g_strdup(purple_status_get_attr_string(status, "message"));
2474 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
2475 if (sip->presence_method_version != 1) {
2476 send_presence_info_v0(sip, note);
2477 } else {
2478 send_presence_info_v1(sip, note);
2482 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
2484 gboolean found = FALSE;
2485 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
2486 if (msg->response == 0) { /* request */
2487 if (!strcmp(msg->method, "MESSAGE")) {
2488 process_incoming_message(sip, msg);
2489 found = TRUE;
2490 } else if (!strcmp(msg->method, "NOTIFY")) {
2491 purple_debug_info("sipe","send->process_incoming_notify\n");
2492 process_incoming_notify(sip, msg);
2493 found = TRUE;
2494 } else if (!strcmp(msg->method, "BENOTIFY")) {
2495 purple_debug_info("sipe","send->process_incoming_benotify\n");
2496 process_incoming_benotify(sip, msg);
2497 found = TRUE;
2498 } else if (!strcmp(msg->method, "INVITE")) {
2499 process_incoming_invite(sip, msg);
2500 found = TRUE;
2501 } else if (!strcmp(msg->method, "INFO")) {
2502 // TODO needs work
2503 gchar * from = parse_from(sipmsg_find_header(msg, "From"));
2504 if (from) {
2505 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
2507 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2508 found = TRUE;
2509 } else if (!strcmp(msg->method, "ACK")) {
2510 // ACK's don't need any response
2511 found = TRUE;
2512 } else if (!strcmp(msg->method, "BYE")) {
2513 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2515 gchar * from = parse_from(sipmsg_find_header(msg, "From"));
2516 struct sip_im_session * session = find_im_session (sip, from);
2517 g_free(from);
2519 if (session) {
2520 // TODO Let the user know the other user left the conversation?
2521 im_session_destroy(sip, session);
2524 found = TRUE;
2525 } else {
2526 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2528 } else { /* response */
2529 struct transaction *trans = transactions_find(sip, msg);
2530 if (trans) {
2531 if (msg->response == 407) {
2532 gchar *resend, *auth, *ptmp;
2534 if (sip->proxy.retries > 30) return;
2535 sip->proxy.retries++;
2536 /* do proxy authentication */
2538 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
2540 fill_auth(sip, ptmp, &sip->proxy);
2541 auth = auth_header(sip, &sip->proxy, trans->msg);
2542 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
2543 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
2544 g_free(auth);
2545 resend = sipmsg_to_string(trans->msg);
2546 /* resend request */
2547 sendout_pkt(sip->gc, resend);
2548 g_free(resend);
2549 } else {
2550 if (msg->response == 100 || msg->response == 180) {
2551 /* ignore provisional response */
2552 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
2553 } else {
2554 sip->proxy.retries = 0;
2555 if (!strcmp(trans->msg->method, "REGISTER")) {
2556 if (msg->response == 401) sip->registrar.retries++;
2557 else sip->registrar.retries = 0;
2558 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
2559 } else {
2560 if (msg->response == 401) {
2561 gchar *resend, *auth, *ptmp;
2563 if (sip->registrar.retries > 4) return;
2564 sip->registrar.retries++;
2566 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
2567 ptmp = sipmsg_find_auth_header(msg, "NTLM");
2568 } else {
2569 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
2572 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
2574 fill_auth(sip, ptmp, &sip->registrar);
2575 auth = auth_header(sip, &sip->registrar, trans->msg);
2576 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
2577 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
2579 //sipmsg_remove_header(trans->msg, "Authorization");
2580 //sipmsg_add_header(trans->msg, "Authorization", auth);
2581 g_free(auth);
2582 resend = sipmsg_to_string(trans->msg);
2583 /* resend request */
2584 sendout_pkt(sip->gc, resend);
2585 g_free(resend);
2589 if (trans->callback) {
2590 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
2591 /* call the callback to process response*/
2592 (trans->callback)(sip, msg, trans);
2594 /* Not sure if this is needed or what needs to be done
2595 but transactions seem to be removed prematurely so
2596 this only removes them if the response is 200 OK */
2597 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
2598 /*Has a bug and it's unneccesary*/
2599 /*transactions_remove(sip, trans);*/
2603 found = TRUE;
2604 } else {
2605 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
2608 if (!found) {
2609 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
2613 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
2615 char *cur;
2616 char *dummy;
2617 struct sipmsg *msg;
2618 int restlen;
2619 cur = conn->inbuf;
2621 /* according to the RFC remove CRLF at the beginning */
2622 while (*cur == '\r' || *cur == '\n') {
2623 cur++;
2625 if (cur != conn->inbuf) {
2626 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
2627 conn->inbufused = strlen(conn->inbuf);
2630 /* Received a full Header? */
2631 while ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
2632 time_t currtime = time(NULL);
2633 cur += 2;
2634 cur[0] = '\0';
2635 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
2636 msg = sipmsg_parse_header(conn->inbuf);
2637 cur[0] = '\r';
2638 cur += 2;
2639 restlen = conn->inbufused - (cur - conn->inbuf);
2640 if (restlen >= msg->bodylen) {
2641 dummy = g_malloc(msg->bodylen + 1);
2642 memcpy(dummy, cur, msg->bodylen);
2643 dummy[msg->bodylen] = '\0';
2644 msg->body = dummy;
2645 cur += msg->bodylen;
2646 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
2647 conn->inbufused = strlen(conn->inbuf);
2648 } else {
2649 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
2650 restlen, msg->bodylen, strlen(conn->inbuf));
2651 sipmsg_free(msg);
2652 return;
2655 if (msg->body) {
2656 purple_debug_info("sipe", "body:\n%s", msg->body);
2659 // Verify the signature before processing it
2660 if (sip->registrar.ntlm_key) {
2661 struct sipmsg_breakdown msgbd;
2662 msgbd.msg = msg;
2663 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
2664 gchar * signature_input_str = sipmsg_breakdown_get_string(&msgbd);
2665 gchar * signature;
2666 if (signature_input_str != NULL) {
2667 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
2670 gchar * rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
2672 if (signature != NULL) {
2673 if (rspauth != NULL) {
2674 if (purple_ntlm_verify_signature (signature, rspauth)) {
2675 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
2676 process_input_message(sip, msg);
2677 } else {
2678 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
2679 purple_connection_error(sip->gc, _("Invalid message signature received"));
2680 sip->gc->wants_to_die = TRUE;
2682 } else if (msg->response == 401) {
2683 purple_connection_error(sip->gc, _("Wrong Password"));
2684 sip->gc->wants_to_die = TRUE;
2688 sipmsg_breakdown_free(&msgbd);
2689 } else {
2690 process_input_message(sip, msg);
2695 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
2697 PurpleConnection *gc = data;
2698 struct sipe_account_data *sip = gc->proto_data;
2699 struct sipmsg *msg;
2700 int len;
2701 time_t currtime;
2703 static char buffer[65536];
2704 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
2705 buffer[len] = '\0';
2706 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
2707 msg = sipmsg_parse_msg(buffer);
2708 if (msg) process_input_message(sip, msg);
2712 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
2714 struct sipe_account_data *sip = gc->proto_data;
2715 PurpleSslConnection *gsc = sip->gsc;
2717 purple_debug_error("sipe", debug);
2718 purple_connection_error(gc, msg);
2720 /* Invalidate this connection. Next send will open a new one */
2721 connection_remove(sip, gsc->fd);
2722 purple_ssl_close(gsc);
2723 sip->gsc = NULL;
2724 sip->fd = -1;
2727 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
2729 PurpleConnection *gc = data;
2730 struct sipe_account_data *sip;
2731 struct sip_connection *conn;
2732 int readlen, len;
2733 gboolean firstread = TRUE;
2735 /* NOTE: This check *IS* necessary */
2736 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
2737 purple_ssl_close(gsc);
2738 return;
2741 sip = gc->proto_data;
2742 conn = connection_find(sip, gsc->fd);
2743 if (conn == NULL) {
2744 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
2745 gc->wants_to_die = TRUE;
2746 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
2747 return;
2750 /* Read all available data from the SSL connection */
2751 do {
2752 /* Increase input buffer size as needed */
2753 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
2754 conn->inbuflen += SIMPLE_BUF_INC;
2755 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
2756 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
2759 /* Try to read as much as there is space left in the buffer */
2760 readlen = conn->inbuflen - conn->inbufused - 1;
2761 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
2763 if (len < 0 && errno == EAGAIN) {
2764 /* Try again later */
2765 return;
2766 } else if (len < 0) {
2767 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
2768 return;
2769 } else if (firstread && (len == 0)) {
2770 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
2771 return;
2774 conn->inbufused += len;
2775 firstread = FALSE;
2777 /* Equivalence indicates that there is possibly more data to read */
2778 } while (len == readlen);
2780 conn->inbuf[conn->inbufused] = '\0';
2781 process_input(sip, conn);
2785 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
2787 PurpleConnection *gc = data;
2788 struct sipe_account_data *sip = gc->proto_data;
2789 int len;
2790 struct sip_connection *conn = connection_find(sip, source);
2791 if (!conn) {
2792 purple_debug_error("sipe", "Connection not found!\n");
2793 return;
2796 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
2797 conn->inbuflen += SIMPLE_BUF_INC;
2798 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
2801 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
2803 if (len < 0 && errno == EAGAIN)
2804 return;
2805 else if (len <= 0) {
2806 purple_debug_info("sipe", "sipe_input_cb: read error\n");
2807 connection_remove(sip, source);
2808 if (sip->fd == source) sip->fd = -1;
2809 return;
2812 conn->inbufused += len;
2813 conn->inbuf[conn->inbufused] = '\0';
2815 process_input(sip, conn);
2818 /* Callback for new connections on incoming TCP port */
2819 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
2821 PurpleConnection *gc = data;
2822 struct sipe_account_data *sip = gc->proto_data;
2823 struct sip_connection *conn;
2825 int newfd = accept(source, NULL, NULL);
2827 conn = connection_create(sip, newfd);
2829 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
2832 static void login_cb(gpointer data, gint source, const gchar *error_message)
2834 PurpleConnection *gc = data;
2835 struct sipe_account_data *sip;
2836 struct sip_connection *conn;
2838 if (!PURPLE_CONNECTION_IS_VALID(gc))
2840 if (source >= 0)
2841 close(source);
2842 return;
2845 if (source < 0) {
2846 purple_connection_error(gc, _("Could not connect"));
2847 return;
2850 sip = gc->proto_data;
2851 sip->fd = source;
2852 sip->last_keepalive = time(NULL);
2854 conn = connection_create(sip, source);
2856 sip->registertimeout = purple_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
2858 do_register(sip);
2860 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
2863 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
2865 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
2866 if (sip == NULL) return;
2868 sip->registertimeout = purple_timeout_add((rand()%100) + 1000, (GSourceFunc)subscribe_timeout, sip);
2869 do_register(sip);
2872 static guint sipe_ht_hash_nick(const char *nick)
2874 char *lc = g_utf8_strdown(nick, -1);
2875 guint bucket = g_str_hash(lc);
2876 g_free(lc);
2878 return bucket;
2881 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
2883 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
2886 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
2888 struct sipe_account_data *sip = (struct sipe_account_data*) data;
2890 sip->listen_data = NULL;
2892 if (listenfd == -1) {
2893 purple_connection_error(sip->gc, _("Could not create listen socket"));
2894 return;
2897 sip->fd = listenfd;
2899 sip->listenport = purple_network_get_port_from_fd(sip->fd);
2900 sip->listenfd = sip->fd;
2902 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
2904 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
2905 sip->registertimeout = purple_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
2906 do_register(sip);
2909 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
2911 struct sipe_account_data *sip = (struct sipe_account_data*) data;
2912 int addr_size;
2914 sip->query_data = NULL;
2916 if (!hosts || !hosts->data) {
2917 purple_connection_error(sip->gc, _("Couldn't resolve host"));
2918 return;
2921 addr_size = GPOINTER_TO_INT(hosts->data);
2922 hosts = g_slist_remove(hosts, hosts->data);
2923 memcpy(&(sip->serveraddr), hosts->data, addr_size);
2924 g_free(hosts->data);
2925 hosts = g_slist_remove(hosts, hosts->data);
2926 while (hosts) {
2927 hosts = g_slist_remove(hosts, hosts->data);
2928 g_free(hosts->data);
2929 hosts = g_slist_remove(hosts, hosts->data);
2932 /* create socket for incoming connections */
2933 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
2934 sipe_udp_host_resolved_listen_cb, sip);
2935 if (sip->listen_data == NULL) {
2936 purple_connection_error(sip->gc, _("Could not create listen socket"));
2937 return;
2941 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
2942 gpointer data)
2944 PurpleConnection *gc = data;
2945 struct sipe_account_data *sip;
2947 /* If the connection is already disconnected, we don't need to do anything else */
2948 if (!PURPLE_CONNECTION_IS_VALID(gc))
2949 return;
2951 sip = gc->proto_data;
2952 sip->fd = -1;
2953 sip->gsc = NULL;
2955 switch(error) {
2956 case PURPLE_SSL_CONNECT_FAILED:
2957 purple_connection_error(gc, _("Connection Failed"));
2958 break;
2959 case PURPLE_SSL_HANDSHAKE_FAILED:
2960 purple_connection_error(gc, _("SSL Handshake Failed"));
2961 break;
2965 static void
2966 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
2968 struct sipe_account_data *sip = (struct sipe_account_data*) data;
2969 PurpleProxyConnectData *connect_data;
2971 sip->listen_data = NULL;
2973 sip->listenfd = listenfd;
2974 if (sip->listenfd == -1) {
2975 purple_connection_error(sip->gc, _("Could not create listen socket"));
2976 return;
2979 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
2980 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
2981 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
2982 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
2983 sipe_newconn_cb, sip->gc);
2984 purple_debug_info("sipe", "connecting to %s port %d\n",
2985 sip->realhostname, sip->realport);
2986 /* open tcp connection to the server */
2987 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
2988 sip->realport, login_cb, sip->gc);
2990 if (connect_data == NULL) {
2991 purple_connection_error(sip->gc, _("Couldn't create socket"));
2996 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
2998 PurpleAccount *account = sip->account;
2999 PurpleConnection *gc = sip->gc;
3001 if (purple_account_get_bool(account, "useport", FALSE)) {
3002 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
3003 port = purple_account_get_int(account, "port", 0);
3004 } else {
3005 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
3008 sip->realhostname = hostname;
3009 sip->realport = port;
3011 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
3012 hostname, port);
3014 /* TODO: is there a good default grow size? */
3015 if (sip->transport != SIPE_TRANSPORT_UDP)
3016 sip->txbuf = purple_circ_buffer_new(0);
3018 if (sip->transport == SIPE_TRANSPORT_TLS) {
3019 /* SSL case */
3020 if (!purple_ssl_is_supported()) {
3021 gc->wants_to_die = TRUE;
3022 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
3023 return;
3026 purple_debug_info("sipe", "using SSL\n");
3028 sip->gsc = purple_ssl_connect(account, hostname, port,
3029 login_cb_ssl, sipe_ssl_connect_failure, gc);
3030 if (sip->gsc == NULL) {
3031 purple_connection_error(gc, _("Could not create SSL context"));
3032 return;
3034 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
3035 /* UDP case */
3036 purple_debug_info("sipe", "using UDP\n");
3038 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
3039 if (sip->query_data == NULL) {
3040 purple_connection_error(gc, _("Could not resolve hostname"));
3042 } else {
3043 /* TCP case */
3044 purple_debug_info("sipe", "using TCP\n");
3045 /* create socket for incoming connections */
3046 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
3047 sipe_tcp_connect_listen_cb, sip);
3048 if (sip->listen_data == NULL) {
3049 purple_connection_error(gc, _("Could not create listen socket"));
3050 return;
3055 /* Service list for autodection */
3056 static const struct sipe_service_data service_autodetect[] = {
3057 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
3058 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
3059 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
3060 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
3061 { NULL, NULL, 0 }
3064 /* Service list for SSL/TLS */
3065 static const struct sipe_service_data service_tls[] = {
3066 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
3067 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
3068 { NULL, NULL, 0 }
3071 /* Service list for TCP */
3072 static const struct sipe_service_data service_tcp[] = {
3073 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
3074 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
3075 { NULL, NULL, 0 }
3078 /* Service list for UDP */
3079 static const struct sipe_service_data service_udp[] = {
3080 { "sip", "udp", SIPE_TRANSPORT_UDP },
3081 { NULL, NULL, 0 }
3084 static void srvresolved(PurpleSrvResponse *, int, gpointer);
3085 static void resolve_next_service(struct sipe_account_data *sip,
3086 const struct sipe_service_data *start)
3088 if (start) {
3089 sip->service_data = start;
3090 } else {
3091 sip->service_data++;
3092 if (sip->service_data->service == NULL) {
3093 /* Try connecting to the SIP hostname directly */
3094 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
3095 // If SSL is supported, default to using it; OCS servers aren't configured
3096 // by default to accept TCP
3097 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
3098 gchar * hostname = g_strdup(sip->sipdomain);
3099 create_connection(sip, hostname, 0);
3100 return;
3104 /* Try to resolve next service */
3105 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
3106 sip->service_data->transport,
3107 sip->sipdomain,
3108 srvresolved, sip);
3111 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
3113 struct sipe_account_data *sip = data;
3115 sip->srv_query_data = NULL;
3117 /* find the host to connect to */
3118 if (results) {
3119 gchar *hostname = g_strdup(resp->hostname);
3120 int port = resp->port;
3121 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
3122 hostname, port);
3123 g_free(resp);
3125 sip->transport = sip->service_data->type;
3127 create_connection(sip, hostname, port);
3128 } else {
3129 resolve_next_service(sip, NULL);
3133 static void sipe_login(PurpleAccount *account)
3135 PurpleConnection *gc;
3136 struct sipe_account_data *sip;
3137 gchar **userserver;
3138 const char *transport;
3140 const char *username = purple_account_get_username(account);
3141 gc = purple_account_get_connection(account);
3143 if (strpbrk(username, " \t\v\r\n") != NULL) {
3144 gc->wants_to_die = TRUE;
3145 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
3146 return;
3149 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
3150 sip->gc = gc;
3151 sip->account = account;
3152 sip->registerexpire = 900;
3154 userserver = g_strsplit(username, "@", 2);
3155 purple_connection_set_display_name(gc, userserver[0]);
3156 sip->username = g_strdup(g_strjoin("@", userserver[0], userserver[1], NULL));
3157 sip->sipdomain = g_strdup(userserver[1]);
3158 sip->password = g_strdup(purple_connection_get_password(gc));
3159 g_strfreev(userserver);
3161 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
3163 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
3165 /* TODO: Set the status correctly. */
3166 sip->status = g_strdup("available");
3168 transport = purple_account_get_string(account, "transport", "auto");
3170 if (purple_account_get_bool(account, "useproxy", FALSE)) {
3171 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
3172 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
3173 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
3174 SIPE_TRANSPORT_UDP;
3175 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
3176 } else if (strcmp(transport, "auto") == 0) {
3177 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
3178 } else if (strcmp(transport, "tls") == 0) {
3179 resolve_next_service(sip, service_tls);
3180 } else if (strcmp(transport, "tcp") == 0) {
3181 resolve_next_service(sip, service_tcp);
3182 } else {
3183 resolve_next_service(sip, service_udp);
3187 static void sipe_connection_cleanup(struct sipe_account_data *sip)
3189 connection_free_all(sip);
3191 if (sip->query_data != NULL)
3192 purple_dnsquery_destroy(sip->query_data);
3193 sip->query_data == NULL;
3195 if (sip->srv_query_data != NULL)
3196 purple_srv_cancel(sip->srv_query_data);
3197 sip->srv_query_data = NULL;
3199 if (sip->listen_data != NULL)
3200 purple_network_listen_cancel(sip->listen_data);
3201 sip->listen_data = NULL;
3203 if (sip->gsc != NULL)
3204 purple_ssl_close(sip->gsc);
3205 sip->gsc = NULL;
3207 g_free(sip->registrar.nonce);
3208 sip->registrar.nonce = NULL;
3209 g_free(sip->registrar.opaque);
3210 sip->registrar.opaque = NULL;
3211 g_free(sip->registrar.realm);
3212 sip->registrar.realm = NULL;
3213 g_free(sip->registrar.target);
3214 sip->registrar.target = NULL;
3215 g_free(sip->registrar.digest_session_key);
3216 sip->registrar.digest_session_key = NULL;
3217 g_free(sip->registrar.ntlm_key);
3218 sip->registrar.ntlm_key = NULL;
3219 sip->registrar.type = 0;
3220 sip->registrar.retries = 0;
3222 g_free(sip->proxy.nonce);
3223 sip->proxy.nonce = NULL;
3224 g_free(sip->proxy.opaque);
3225 sip->proxy.opaque = NULL;
3226 g_free(sip->proxy.realm);
3227 sip->proxy.realm = NULL;
3228 g_free(sip->proxy.target);
3229 sip->proxy.target = NULL;
3230 g_free(sip->proxy.digest_session_key);
3231 sip->proxy.digest_session_key = NULL;
3232 g_free(sip->proxy.ntlm_key);
3233 sip->proxy.ntlm_key = NULL;
3234 sip->proxy.type = 0;
3235 sip->proxy.retries = 0;
3237 if (sip->txbuf)
3238 purple_circ_buffer_destroy(sip->txbuf);
3239 sip->txbuf = NULL;
3241 g_free(sip->realhostname);
3242 sip->realhostname = NULL;
3244 if (sip->listenpa)
3245 purple_input_remove(sip->listenpa);
3246 sip->listenpa = 0;
3247 if (sip->tx_handler)
3248 purple_input_remove(sip->tx_handler);
3249 sip->tx_handler = 0;
3250 if (sip->resendtimeout)
3251 purple_timeout_remove(sip->resendtimeout);
3252 sip->resendtimeout = 0;
3253 if (sip->registertimeout)
3254 purple_timeout_remove(sip->registertimeout);
3255 sip->registertimeout = 0;
3257 sip->fd = -1;
3260 static void sipe_close(PurpleConnection *gc)
3262 struct sipe_account_data *sip = gc->proto_data;
3264 if (sip) {
3265 /* leave all conversations */
3266 im_session_close_all(sip);
3268 /* unregister */
3269 do_register_exp(sip, 0);
3271 sipe_connection_cleanup(sip);
3272 g_free(sip->sipdomain);
3273 g_free(sip->username);
3274 g_free(sip->password);
3276 g_free(gc->proto_data);
3277 gc->proto_data = NULL;
3280 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
3282 PurpleAccount *acct = purple_connection_get_account(gc);
3283 char *id = g_strdup_printf("sip:%s", g_list_nth_data(row, 0));
3284 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
3285 if (conv == NULL)
3286 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
3287 purple_conversation_present(conv);
3288 g_free(id);
3291 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
3294 purple_blist_request_add_buddy(purple_connection_get_account(gc),
3295 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
3298 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
3300 PurpleNotifySearchResults *results;
3301 PurpleNotifySearchColumn *column;
3302 xmlnode *searchResults;
3303 xmlnode *mrow;
3305 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
3306 if (!searchResults) {
3307 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
3308 return FALSE;
3311 results = purple_notify_searchresults_new();
3313 if (results == NULL) {
3314 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
3315 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
3317 xmlnode_free(searchResults);
3318 return FALSE;
3321 column = purple_notify_searchresults_column_new(_("User Name"));
3322 purple_notify_searchresults_column_add(results, column);
3324 column = purple_notify_searchresults_column_new(_("Name"));
3325 purple_notify_searchresults_column_add(results, column);
3327 column = purple_notify_searchresults_column_new(_("Company"));
3328 purple_notify_searchresults_column_add(results, column);
3330 column = purple_notify_searchresults_column_new(_("Country"));
3331 purple_notify_searchresults_column_add(results, column);
3333 column = purple_notify_searchresults_column_new(_("Email"));
3334 purple_notify_searchresults_column_add(results, column);
3336 int match_count = 0;
3337 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
3338 GList *row = NULL;
3340 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
3341 row = g_list_append(row, g_strdup(uri_parts[1]));
3342 g_strfreev(uri_parts);
3344 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
3345 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
3346 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
3347 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
3349 purple_notify_searchresults_row_add(results, row);
3350 match_count++;
3353 gboolean more = FALSE;
3354 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
3355 char *data = xmlnode_get_data_unescaped(mrow);
3356 more = (g_strcasecmp(data, "true") == 0);
3357 g_free(data);
3360 gchar *secondary = g_strdup_printf(
3361 dngettext(GETTEXT_PACKAGE,
3362 "Found %d contact%s:",
3363 "Found %d contacts%s:", match_count),
3364 match_count, more ? _(" (more matched your query)") : "");
3366 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
3367 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
3368 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
3370 g_free(secondary);
3371 xmlnode_free(searchResults);
3372 return TRUE;
3375 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
3377 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
3378 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
3379 unsigned i = 0;
3381 do {
3382 PurpleRequestField *field = entries->data;
3383 const char *id = purple_request_field_get_id(field);
3384 const char *value = purple_request_field_string_get_value(field);
3386 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value);
3388 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
3389 } while ((entries = g_list_next(entries)) != NULL);
3390 attrs[i] = NULL;
3392 if (i > 0) {
3393 gchar *query = g_strjoinv(NULL, attrs);
3394 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
3395 send_soap_request_with_cb(gc->proto_data, body,
3396 (TransCallback) process_search_contact_response, NULL);
3397 g_free(body);
3398 g_free(query);
3401 g_strfreev(attrs);
3404 static void sipe_show_find_contact(PurplePluginAction *action)
3406 PurpleConnection *gc = (PurpleConnection *) action->context;
3407 PurpleRequestFields *fields;
3408 PurpleRequestFieldGroup *group;
3409 PurpleRequestField *field;
3411 fields = purple_request_fields_new();
3412 group = purple_request_field_group_new(NULL);
3413 purple_request_fields_add_group(fields, group);
3415 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
3416 purple_request_field_group_add_field(group, field);
3417 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
3418 purple_request_field_group_add_field(group, field);
3419 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
3420 purple_request_field_group_add_field(group, field);
3421 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
3422 purple_request_field_group_add_field(group, field);
3424 purple_request_fields(gc,
3425 _("Search"),
3426 _("Search for a Contact"),
3427 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
3428 fields,
3429 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
3430 _("_Cancel"), NULL,
3431 purple_connection_get_account(gc), NULL, NULL, gc);
3434 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
3436 PurpleConnection *gc = (PurpleConnection *) context;
3437 struct sipe_account_data *sip = gc->proto_data;
3438 GList *menu = NULL;
3439 PurplePluginAction *act;
3441 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
3442 menu = g_list_prepend(menu, act);
3444 menu = g_list_reverse(menu);
3446 return menu;
3449 /* not needed since privacy is checked for every subscribe */
3450 static void dummy_add_deny(PurpleConnection *gc, const char *name) {
3453 static void dummy_permit_deny(PurpleConnection *gc)
3457 static gboolean sipe_plugin_load(PurplePlugin *plugin)
3459 return TRUE;
3463 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
3465 return TRUE;
3469 static void sipe_plugin_destroy(PurplePlugin *plugin)
3473 static PurplePlugin *my_protocol = NULL;
3475 static PurplePluginProtocolInfo prpl_info =
3478 NULL, /* user_splits */
3479 NULL, /* protocol_options */
3480 NO_BUDDY_ICONS, /* icon_spec */
3481 sipe_list_icon, /* list_icon */
3482 NULL, /* list_emblems */
3483 NULL, /* status_text */ // TODO
3484 NULL, /* tooltip_text */ // add custom info to contact tooltip
3485 sipe_status_types, /* away_states */
3486 NULL, /* blist_node_menu */
3487 NULL, /* chat_info */
3488 NULL, /* chat_info_defaults */
3489 sipe_login, /* login */
3490 sipe_close, /* close */
3491 sipe_im_send, /* send_im */
3492 NULL, /* set_info */ // TODO maybe
3493 sipe_send_typing, /* send_typing */
3494 NULL, /* get_info */ // TODO maybe
3495 sipe_set_status, /* set_status */
3496 NULL, /* set_idle */
3497 NULL, /* change_passwd */
3498 sipe_add_buddy, /* add_buddy */
3499 NULL, /* add_buddies */
3500 sipe_remove_buddy, /* remove_buddy */
3501 NULL, /* remove_buddies */
3502 dummy_add_deny, /* add_permit */
3503 dummy_add_deny, /* add_deny */
3504 dummy_add_deny, /* rem_permit */
3505 dummy_add_deny, /* rem_deny */
3506 dummy_permit_deny, /* set_permit_deny */
3507 NULL, /* join_chat */
3508 NULL, /* reject_chat */
3509 NULL, /* get_chat_name */
3510 NULL, /* chat_invite */
3511 NULL, /* chat_leave */
3512 NULL, /* chat_whisper */
3513 NULL, /* chat_send */
3514 sipe_keep_alive, /* keepalive */
3515 NULL, /* register_user */
3516 NULL, /* get_cb_info */ // deprecated
3517 NULL, /* get_cb_away */ // deprecated
3518 sipe_alias_buddy, /* alias_buddy */
3519 sipe_group_buddy, /* group_buddy */
3520 sipe_rename_group, /* rename_group */
3521 NULL, /* buddy_free */
3522 sipe_convo_closed, /* convo_closed */
3523 purple_normalize_nocase, /* normalize */
3524 NULL, /* set_buddy_icon */
3525 sipe_remove_group, /* remove_group */
3526 NULL, /* get_cb_real_name */ // TODO?
3527 NULL, /* set_chat_topic */
3528 NULL, /* find_blist_chat */
3529 NULL, /* roomlist_get_list */
3530 NULL, /* roomlist_cancel */
3531 NULL, /* roomlist_expand_category */
3532 NULL, /* can_receive_file */
3533 NULL, /* send_file */
3534 NULL, /* new_xfer */
3535 NULL, /* offline_message */
3536 NULL, /* whiteboard_prpl_ops */
3537 sipe_send_raw, /* send_raw */
3541 static PurplePluginInfo info = {
3542 PURPLE_PLUGIN_MAGIC,
3543 PURPLE_MAJOR_VERSION,
3544 PURPLE_MINOR_VERSION,
3545 PURPLE_PLUGIN_PROTOCOL, /**< type */
3546 NULL, /**< ui_requirement */
3547 0, /**< flags */
3548 NULL, /**< dependencies */
3549 PURPLE_PRIORITY_DEFAULT, /**< priority */
3550 "prpl-sipe", /**< id */
3551 "Microsoft LCS/OCS", /**< name */
3552 VERSION, /**< version */
3553 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
3554 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
3555 "Anibal Avelar <avelar@gmail.com>, " /**< author */
3556 "Gabriel Burt <gburt@novell.com>", /**< author */
3557 PURPLE_WEBSITE, /**< homepage */
3558 sipe_plugin_load, /**< load */
3559 sipe_plugin_unload, /**< unload */
3560 sipe_plugin_destroy, /**< destroy */
3561 NULL, /**< ui_info */
3562 &prpl_info, /**< extra_info */
3563 NULL,
3564 sipe_actions,
3565 NULL,
3566 NULL,
3567 NULL,
3568 NULL
3571 static void init_plugin(PurplePlugin *plugin)
3573 PurpleAccountUserSplit *split;
3574 PurpleAccountOption *option;
3575 PurpleKeyValuePair *kvp;
3577 #ifdef ENABLE_NLS
3578 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
3579 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
3580 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
3581 #endif
3583 purple_plugin_register(plugin);
3585 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
3586 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3587 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
3588 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3590 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
3591 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3592 // Translators: noun (networking port)
3593 option = purple_account_option_int_new(_("Port"), "port", 5061);
3594 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3596 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
3597 purple_account_option_add_list_item(option, _("Auto"), "auto");
3598 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
3599 purple_account_option_add_list_item(option, _("TCP"), "tcp");
3600 purple_account_option_add_list_item(option, _("UDP"), "udp");
3601 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3603 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
3604 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
3606 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
3607 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3609 // TODO commented out so won't show in the preferences until we fix krb message signing
3610 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
3611 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3613 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
3614 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
3615 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3618 option = purple_account_option_string_new(_("Auth User"), "authuser", "");
3619 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3620 option = purple_account_option_string_new(_("Auth Domain"), "authdomain", "");
3621 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3622 my_protocol = plugin;
3625 /* I had to redefined the function for it load, but works */
3626 gboolean purple_init_plugin(PurplePlugin *plugin){
3627 plugin->info = &(info);
3628 init_plugin((plugin));
3629 sipe_plugin_load((plugin));
3630 return purple_plugin_register(plugin);