Update Changelog
[siplcs.git] / src / sipe.c
blob4f0ee1ff2a0e3ccecaad5490d11e9d98f9566b30
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 <errno.h>
49 #include <string.h>
50 #include <glib.h>
52 #ifdef ENABLE_NLS
53 #include <libintl.h>
54 # define _(String) ((const char *) gettext (String))
55 #else
56 # define _(String) ((const char *) (String))
57 #endif /* ENABLE_NLS */
59 #include "accountopt.h"
60 #include "blist.h"
61 #include "conversation.h"
62 #include "dnsquery.h"
63 #include "debug.h"
64 #include "notify.h"
65 #include "privacy.h"
66 #include "prpl.h"
67 #include "plugin.h"
68 #include "util.h"
69 #include "version.h"
70 #include "network.h"
71 #include "xmlnode.h"
73 #include "sipe.h"
74 #include "sip-ntlm.h"
75 #ifdef USE_KERBEROS
76 #include "sipkrb5.h"
77 #endif /*USE_KERBEROS*/
79 #include "sipmsg.h"
80 #include "sipe-sign.h"
81 #include "dnssrv.h"
82 #include "request.h"
84 /* Keep in sync with sipe_transport_type! */
85 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
86 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
88 static char *gentag()
90 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
93 static gchar *get_epid()
95 return sipe_uuid_get_macaddr();
98 static char *genbranch()
100 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
101 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
102 rand() & 0xFFFF, rand() & 0xFFFF);
105 static char *gencallid()
107 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
108 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
109 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
110 rand() & 0xFFFF, rand() & 0xFFFF);
113 static gchar *find_tag(const gchar *hdr)
115 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
116 if (!tag) {
117 // In case it's at the end and there's no trailing ;
118 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
120 return tag;
124 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
126 return "sipe";
129 static void sipe_keep_alive(PurpleConnection *gc)
131 struct sipe_account_data *sip = gc->proto_data;
132 if (sip->transport == SIPE_TRANSPORT_UDP) {
133 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
134 gchar buf[2] = {0, 0};
135 purple_debug_info("sipe", "sending keep alive\n");
136 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
140 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
142 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
143 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
144 gpointer data);
146 static void send_notify(struct sipe_account_data *sip, struct sipe_watcher *);
147 static void sipe_close(PurpleConnection *gc);
149 static void send_service(struct sipe_account_data *sip);
150 static void sipe_subscribe_to_name(struct sipe_account_data *sip, const char * buddy_name);
151 static void send_presence_info(struct sipe_account_data *sip);
153 static void do_notifies(struct sipe_account_data *sip)
155 GSList *tmp = sip->watcher;
156 purple_debug_info("sipe", "do_notifies()\n");
158 while (tmp) {
159 purple_debug_info("sipe", "notifying %s\n", ((struct sipe_watcher*)tmp->data)->name);
160 send_notify(sip, tmp->data);
161 tmp = tmp->next;
165 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
167 struct sip_connection *ret = NULL;
168 GSList *entry = sip->openconns;
169 while (entry) {
170 ret = entry->data;
171 if (ret->fd == fd) return ret;
172 entry = entry->next;
174 return NULL;
177 static struct sipe_watcher *watcher_find(struct sipe_account_data *sip,
178 const gchar *name)
180 struct sipe_watcher *watcher;
181 GSList *entry = sip->watcher;
182 while (entry) {
183 watcher = entry->data;
184 if (!strcmp(name, watcher->name)) return watcher;
185 entry = entry->next;
187 return NULL;
190 static struct sipe_watcher *watcher_create(struct sipe_account_data *sip,
191 const gchar *name, const gchar *callid, const gchar *ourtag,
192 const gchar *theirtag, gboolean needsxpidf)
194 struct sipe_watcher *watcher = g_new0(struct sipe_watcher, 1);
195 watcher->name = g_strdup(name);
196 watcher->dialog.callid = g_strdup(callid);
197 watcher->dialog.ourtag = g_strdup(ourtag);
198 watcher->dialog.theirtag = g_strdup(theirtag);
199 watcher->needsxpidf = needsxpidf;
200 sip->watcher = g_slist_append(sip->watcher, watcher);
201 return watcher;
204 static void watcher_remove(struct sipe_account_data *sip, const gchar *name)
206 struct sipe_watcher *watcher = watcher_find(sip, name);
207 sip->watcher = g_slist_remove(sip->watcher, watcher);
208 g_free(watcher->name);
209 g_free(watcher->dialog.callid);
210 g_free(watcher->dialog.ourtag);
211 g_free(watcher->dialog.theirtag);
212 g_free(watcher);
215 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
217 struct sip_connection *ret = g_new0(struct sip_connection, 1);
218 ret->fd = fd;
219 sip->openconns = g_slist_append(sip->openconns, ret);
220 return ret;
223 static void connection_remove(struct sipe_account_data *sip, int fd)
225 struct sip_connection *conn = connection_find(sip, fd);
226 sip->openconns = g_slist_remove(sip->openconns, conn);
227 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
228 g_free(conn->inbuf);
229 g_free(conn);
232 static void connection_free_all(struct sipe_account_data *sip)
234 struct sip_connection *ret = NULL;
235 GSList *entry = sip->openconns;
236 while (entry) {
237 ret = entry->data;
238 connection_remove(sip, ret->fd);
239 entry = sip->openconns;
243 static gchar *auth_header_without_newline(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg, gboolean force_reauth)
245 const gchar *method = msg->method;
246 const gchar *target = msg->target;
247 gchar noncecount[9];
248 gchar *response;
249 gchar *ret;
250 gchar *tmp;
251 const char *authdomain;
252 const char *authuser;
253 const char *krb5_realm;
254 const char *host;
255 gchar *krb5_token = NULL;
257 authdomain = purple_account_get_string(sip->account, "authdomain", "");
258 authuser = purple_account_get_string(sip->account, "authuser", sip->username);
260 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
261 // and do error checking
263 // KRB realm should always be uppercase
264 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
266 if (sip->realhostname) {
267 host = sip->realhostname;
268 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
269 host = purple_account_get_string(sip->account, "proxy", "");
270 } else {
271 host = sip->sipdomain;
274 /*gboolean new_auth = krb5_auth.gss_context == NULL;
275 if (new_auth) {
276 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
279 if (new_auth || force_reauth) {
280 krb5_token = krb5_auth.base64_token;
283 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
284 krb5_token = krb5_auth.base64_token;*/
286 if (!authuser || strlen(authuser) < 1) {
287 authuser = sip->username;
290 if (auth->type == 1) { /* Digest */
291 sprintf(noncecount, "%08d", auth->nc++);
292 response = purple_cipher_http_digest_calculate_response(
293 "md5", method, target, NULL, NULL,
294 auth->nonce, noncecount, NULL, auth->digest_session_key);
295 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
297 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);
298 g_free(response);
299 return ret;
300 } else if (auth->type == 2) { /* NTLM */
301 // If we have a signature for the message, include that
302 if (msg->signature) {
303 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);
304 return tmp;
307 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
308 /* TODO: Don't hardcode "purple" as the hostname */
309 const gchar * ntlm_key;
310 gchar * gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, "purple", authdomain, (const guint8 *)auth->nonce, &auth->flags);
311 auth->ntlm_key = (gchar *)ntlm_key;
312 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
313 g_free(gssapi_data);
314 return tmp;
317 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
318 return tmp;
319 } else if (auth->type == 3) {
320 /* Kerberos */
321 if (auth->nc == 3) {
322 /*if (new_auth || force_reauth) {
323 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
324 if (auth->opaque) {
325 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);
326 } else {
327 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
329 } else {
330 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
331 gchar * mic = "MICTODO";
332 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
333 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
334 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
335 //auth->opaque ? auth->opaque : "", auth->target);
336 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
337 //g_free(mic);
339 return tmp;
341 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
344 sprintf(noncecount, "%08d", auth->nc++);
345 response = purple_cipher_http_digest_calculate_response(
346 "md5", method, target, NULL, NULL,
347 auth->nonce, noncecount, NULL, auth->digest_session_key);
348 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
350 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
351 g_free(response);
352 return ret;
355 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg, gboolean force_reauth)
357 gchar *with, *without;
359 without = auth_header_without_newline(sip, auth, msg, force_reauth);
360 with = g_strdup_printf("%s\r\n", without);
361 g_free (without);
362 return with;
366 static char *parse_attribute(const char *attrname, const char *source)
368 const char *tmp, *tmp2;
369 char *retval = NULL;
370 int len = strlen(attrname);
372 if (!strncmp(source, attrname, len)) {
373 tmp = source + len;
374 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
375 if (tmp2)
376 retval = g_strndup(tmp, tmp2 - tmp);
377 else
378 retval = g_strdup(tmp);
381 return retval;
384 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
386 int i = 0;
387 const char *authuser;
388 char *tmp;
389 gchar **parts;
390 const char *krb5_realm;
391 const char *host;
393 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
394 // and do error checking
396 // KRB realm should always be uppercase
397 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
399 if (sip->realhostname) {
400 host = sip->realhostname;
401 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
402 host = purple_account_get_string(sip->account, "proxy", "");
403 } else {
404 host = sip->sipdomain;
407 authuser = purple_account_get_string(sip->account, "authuser", sip->username);
409 if (!authuser || strlen(authuser) < 1) {
410 authuser = sip->username;
413 if (!hdr) {
414 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
415 return;
418 if (!g_strncasecmp(hdr, "NTLM", 4)) {
419 auth->type = 2;
420 parts = g_strsplit(hdr+5, "\", ", 0);
421 i = 0;
422 while (parts[i]) {
423 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
424 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
425 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
426 g_free(tmp);
428 if ((tmp = parse_attribute("targetname=\"",
429 parts[i]))) {
430 auth->target = tmp;
432 else if ((tmp = parse_attribute("realm=\"",
433 parts[i]))) {
434 auth->realm = tmp;
436 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
437 auth->opaque = tmp;
439 i++;
441 g_strfreev(parts);
442 auth->nc = 1;
443 if (!strstr(hdr, "gssapi-data")) {
444 auth->nc = 1;
445 } else {
446 auth->nc = 3;
448 return;
451 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
452 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
453 auth->type = 3;
454 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
455 parts = g_strsplit(hdr+9, "\", ", 0);
456 i = 0;
457 while (parts[i]) {
458 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
459 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
460 /*if (krb5_auth.gss_context == NULL) {
461 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
463 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
464 g_free(tmp);
466 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
467 auth->target = tmp;
468 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
469 auth->realm = tmp;
470 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
471 auth->opaque = tmp;
473 i++;
475 g_strfreev(parts);
476 auth->nc = 3;
477 //if (!strstr(hdr, "gssapi-data")) {
478 // auth->nc = 1;
479 //} else {
480 // auth->nc = 3;
482 return;
485 auth->type = 1;
486 parts = g_strsplit(hdr, " ", 0);
487 while (parts[i]) {
488 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
489 auth->nonce = tmp;
491 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
492 auth->realm = tmp;
494 i++;
496 g_strfreev(parts);
498 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
499 if (auth->realm) {
500 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
501 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
503 auth->nc = 1;
507 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
509 PurpleConnection *gc = data;
510 struct sipe_account_data *sip = gc->proto_data;
511 gsize max_write;
512 gssize written;
514 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
516 if (max_write == 0) {
517 if (sip->tx_handler != 0){
518 purple_input_remove(sip->tx_handler);
519 sip->tx_handler = 0;
521 return;
524 written = write(sip->fd, sip->txbuf->outptr, max_write);
526 if (written < 0 && errno == EAGAIN)
527 written = 0;
528 else if (written <= 0) {
529 /*TODO: do we really want to disconnect on a failure to write?*/
530 purple_connection_error(gc, _("Could not write"));
531 return;
534 purple_circ_buffer_mark_read(sip->txbuf, written);
537 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
539 PurpleConnection *gc = data;
540 struct sipe_account_data *sip = gc->proto_data;
541 gsize max_write;
542 gssize written;
544 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
546 if (max_write == 0) {
547 if (sip->tx_handler != 0) {
548 purple_input_remove(sip->tx_handler);
549 sip->tx_handler = 0;
550 return;
554 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
556 if (written < 0 && errno == EAGAIN)
557 written = 0;
558 else if (written <= 0) {
559 /*TODO: do we really want to disconnect on a failure to write?*/
560 purple_connection_error(gc, _("Could not write"));
561 return;
564 purple_circ_buffer_mark_read(sip->txbuf, written);
567 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
569 static void send_later_cb(gpointer data, gint source, const gchar *error)
571 PurpleConnection *gc = data;
572 struct sipe_account_data *sip;
573 struct sip_connection *conn;
575 if (!PURPLE_CONNECTION_IS_VALID(gc))
577 if (source >= 0)
578 close(source);
579 return;
582 if (source < 0) {
583 purple_connection_error(gc, _("Could not connect"));
584 return;
587 sip = gc->proto_data;
588 sip->fd = source;
589 sip->connecting = FALSE;
591 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
593 /* If there is more to write now, we need to register a handler */
594 if (sip->txbuf->bufused > 0)
595 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
597 conn = connection_create(sip, source);
598 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
601 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
603 PurpleConnection *gc = data;
604 struct sipe_account_data *sip;
605 struct sip_connection *conn;
607 if (!PURPLE_CONNECTION_IS_VALID(gc))
609 if(gsc) purple_ssl_close(gsc);
610 return;
613 sip = gc->proto_data;
614 sip->gsc = gsc;
615 sip->fd = gsc->fd;
616 sip->connecting = FALSE;
618 sipe_canwrite_cb_ssl(gc, sip->gsc->fd, PURPLE_INPUT_WRITE);
620 /* If there is more to write now */
621 if (sip->txbuf->bufused > 0)
623 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
626 conn = connection_create(sip, sip->gsc->fd);
627 purple_ssl_input_add(sip->gsc, sipe_input_cb_ssl, gc);
631 static void sendlater(PurpleConnection *gc, const char *buf)
633 struct sipe_account_data *sip = gc->proto_data;
635 if (!sip->connecting) {
636 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
637 if (sip->transport == SIPE_TRANSPORT_TLS){
638 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
639 } else {
640 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
641 purple_connection_error(gc, _("Couldn't create socket"));
644 sip->connecting = TRUE;
647 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
648 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
650 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
653 static void sendout_pkt(PurpleConnection *gc, const char *buf)
655 struct sipe_account_data *sip = gc->proto_data;
656 time_t currtime = time(NULL);
657 int writelen = strlen(buf);
659 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
660 if (sip->transport == SIPE_TRANSPORT_UDP) {
661 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
662 purple_debug_info("sipe", "could not send packet\n");
664 } else {
665 int ret;
666 if (sip->fd < 0) {
667 sendlater(gc, buf);
668 return;
671 if (sip->tx_handler) {
672 ret = -1;
673 errno = EAGAIN;
674 } else{
675 if (sip->gsc){
676 ret = purple_ssl_write(sip->gsc, buf, writelen);
677 }else{
678 ret = write(sip->fd, buf, writelen);
682 if (ret < 0 && errno == EAGAIN)
683 ret = 0;
684 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
685 sendlater(gc, buf);
686 return;
689 if (ret < writelen) {
690 if (!sip->tx_handler){
691 if (sip->gsc){
692 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
694 else{
695 sip->tx_handler = purple_input_add(sip->fd,
696 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
697 gc);
701 /* XXX: is it OK to do this? You might get part of a request sent
702 with part of another. */
703 if (sip->txbuf->bufused > 0)
704 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
706 purple_circ_buffer_append(sip->txbuf, buf + ret,
707 writelen - ret);
712 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
714 sendout_pkt(gc, buf);
715 return len;
718 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
720 GSList *tmp = msg->headers;
721 gchar *name;
722 gchar *value;
723 GString *outstr = g_string_new("");
724 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
725 while (tmp) {
726 name = ((struct siphdrelement*) (tmp->data))->name;
727 value = ((struct siphdrelement*) (tmp->data))->value;
728 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
729 tmp = g_slist_next(tmp);
731 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
732 sendout_pkt(sip->gc, outstr->str);
733 g_string_free(outstr, TRUE);
736 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
738 gchar * buf;
739 if (sip->registrar.ntlm_key) {
740 struct sipmsg_breakdown msgbd;
741 msgbd.msg = msg;
742 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
743 // TODO generate this
744 msgbd.rand = g_strdup("0878F41B");
745 sip->registrar.ntlm_num++;
746 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
747 gchar * signature_input_str = sipmsg_breakdown_get_string(&msgbd);
748 if (signature_input_str != NULL) {
749 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
750 msg->rand = g_strdup(msgbd.rand);
751 msg->num = g_strdup(msgbd.num);
753 sipmsg_breakdown_free(&msgbd);
756 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
757 buf = auth_header_without_newline(sip, &sip->registrar, msg, FALSE);
758 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
759 sipmsg_add_header(msg, "Authorization", buf);
760 } else {
761 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
762 //sipmsg_add_header_pos(msg, "Authorization", buf, 5);
764 g_free(buf);
765 } 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")) {
766 sip->registrar.nc=3;
767 sip->registrar.type=2;
769 buf = auth_header_without_newline(sip, &sip->registrar, msg, FALSE);
770 //buf = auth_header(sip, &sip->proxy, msg, FALSE);
771 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
772 //sipmsg_add_header(msg, "Authorization", buf);
773 g_free(buf);
774 } else {
775 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
779 static char *get_contact(struct sipe_account_data *sip)
781 return g_strdup(sip->contact);
785 static char *get_contact_service(struct sipe_account_data *sip)
787 return g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, generateUUIDfromEPID(get_epid()));
788 //return g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
791 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
792 const char *text, const char *body)
794 gchar *name;
795 gchar *value;
796 GString *outstr = g_string_new("");
797 struct sipe_account_data *sip = gc->proto_data;
799 gchar *contact;
800 contact = get_contact(sip);
801 sipmsg_remove_header(msg, "Contact");
802 sipmsg_add_header(msg, "Contact", contact);
803 g_free(contact);
805 /* When sending the acknowlegements and errors, the content length from the original
806 message is still here, but there is no body; we need to make sure we're sending the
807 correct content length */
808 sipmsg_remove_header(msg, "Content-Length");
809 if (body) {
810 gchar len[12];
811 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
812 sipmsg_add_header(msg, "Content-Length", len);
813 } else {
814 sipmsg_add_header(msg, "Content-Length", "0");
817 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
818 //gchar * mic = "MICTODO";
819 msg->response = code;
821 sipmsg_remove_header(msg, "Authentication-Info");
822 sign_outgoing_message(msg, sip, msg->method);
824 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
825 GSList *tmp = msg->headers;
826 while (tmp) {
827 name = ((struct siphdrelement*) (tmp->data))->name;
828 value = ((struct siphdrelement*) (tmp->data))->value;
830 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
831 tmp = g_slist_next(tmp);
833 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
834 sendout_pkt(gc, outstr->str);
835 g_string_free(outstr, TRUE);
838 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
840 if (trans->msg) sipmsg_free(trans->msg);
841 sip->transactions = g_slist_remove(sip->transactions, trans);
842 g_free(trans);
845 static struct transaction *
846 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
848 struct transaction *trans = g_new0(struct transaction, 1);
849 trans->time = time(NULL);
850 trans->msg = (struct sipmsg *)msg;
851 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
852 trans->callback = callback;
853 sip->transactions = g_slist_append(sip->transactions, trans);
854 return trans;
857 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
859 struct transaction *trans;
860 GSList *transactions = sip->transactions;
861 gchar *cseq = sipmsg_find_header(msg, "CSeq");
863 while (transactions) {
864 trans = transactions->data;
865 if (!strcmp(trans->cseq, cseq)) {
866 return trans;
868 transactions = transactions->next;
871 return NULL;
874 static struct transaction *
875 send_sip_request(PurpleConnection *gc, const gchar *method,
876 const gchar *url, const gchar *to, const gchar *addheaders,
877 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
879 struct sipe_account_data *sip = gc->proto_data;
880 const char *addh = "";
881 char *buf;
882 struct sipmsg *msg;
883 gchar *ptmp;
884 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
885 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
886 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
887 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
888 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
889 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
890 gchar *route = strdup("");
892 if (dialog && dialog->routes)
894 GSList *iter = dialog->routes;
896 while(iter)
898 char *tmp = route;
899 route = g_strdup_printf("%sRoute: <%s>\r\n", route, iter->data);
900 g_free(tmp);
901 iter = g_slist_next(iter);
905 if (!ourtag && !dialog) {
906 ourtag = gentag();
909 if (!strcmp(method, "REGISTER")) {
910 if (sip->regcallid) {
911 g_free(callid);
912 callid = g_strdup(sip->regcallid);
913 } else {
914 sip->regcallid = g_strdup(callid);
918 if (addheaders) addh = addheaders;
920 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
921 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
922 "From: <sip:%s>%s%s;epid=%s\r\n"
923 "To: <%s>%s%s%s%s\r\n"
924 "Max-Forwards: 70\r\n"
925 "CSeq: %d %s\r\n"
926 "User-Agent: %s\r\n"
927 "Call-ID: %s\r\n"
928 "%s%s"
929 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
930 method,
931 dialog && dialog->request ? dialog->request : url,
932 TRANSPORT_DESCRIPTOR,
933 purple_network_get_my_ip(-1),
934 sip->listenport,
935 branch ? ";branch=" : "",
936 branch ? branch : "",
937 sip->username,
938 ourtag ? ";tag=" : "",
939 ourtag ? ourtag : "",
940 get_epid(), // TODO generate one per account/login
942 theirtag ? ";tag=" : "",
943 theirtag ? theirtag : "",
944 theirepid ? ";epid=" : "",
945 theirepid ? theirepid : "",
946 dialog ? ++dialog->cseq : ++sip->cseq,
947 method,
948 useragent,
949 callid,
950 route,
951 addh,
952 body ? strlen(body) : 0,
953 body ? body : "");
956 //printf ("parsing msg buf:\n%s\n\n", buf);
957 msg = sipmsg_parse_msg(buf);
959 g_free(buf);
960 g_free(ourtag);
961 g_free(theirtag);
962 g_free(theirepid);
963 g_free(branch);
964 g_free(callid);
965 g_free(route);
967 sign_outgoing_message (msg, sip, method);
969 buf = sipmsg_to_string (msg);
971 /* add to ongoing transactions */
972 struct transaction * trans = transactions_add_buf(sip, msg, tc);
973 sendout_pkt(gc, buf);
975 return trans;
978 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
980 gchar *from = g_strdup_printf("sip:%s", sip->username);
981 gchar *hdr = g_strdup("Content-Type: application/SOAP+xml\r\n");
983 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
984 tr->payload = payload;
986 g_free(from);
987 g_free(hdr);
990 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
992 send_soap_request_with_cb(sip, body, NULL, NULL);
995 static char *get_contact_register(struct sipe_account_data *sip)
997 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()));
1000 static void do_register_exp(struct sipe_account_data *sip, int expire)
1002 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1003 char *to = g_strdup_printf("sip:%s", sip->username);
1004 char *contact = get_contact_register(sip);
1005 //char *hdr = g_strdup_printf("Contact: %s\r\nExpires: %d\r\n", contact, expire);
1006 // char *hdr = g_strdup_printf("Contact: %s\r\nEvent: registration\r\nAllow-Events: presence\r\nms-keep-alive: UAC;hop-hop=yes\r\nExpires: %d\r\n", contact,expire);
1007 //char *hdr = g_strdup_printf("Contact: %s\r\nSupported: com.microsoft.msrtc.presence, adhoclist\r\nms-keep-alive: UAC;hop-hop=yes\r\nEvent: registration\r\nAllow-Events: presence\r\n", contact);
1008 //char *hdr = g_strdup_printf("Contact: %s\r\nSupported: com.microsoft.msrtc.presence, gruu-10, adhoclist\r\nEvent: registration\r\nAllow-Events: presence\r\nms-keep-alive: UAC;hop-hop=yes\r\nExpires: %d\r\n", contact,expire);
1009 char *hdr = g_strdup_printf("Contact: %s\r\n"
1010 "Supported: gruu-10, adhoclist\r\n"
1011 "Event: registration\r\n"
1012 "Allow-Events: presence\r\n"
1013 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1014 "Expires: %d\r\n", contact,expire);
1015 g_free(contact);
1017 sip->registerstatus = 1;
1019 if (expire) {
1020 sip->reregister = time(NULL) + expire - 50;
1021 } else {
1022 sip->reregister = time(NULL) + 600;
1025 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1026 process_register_response);
1028 g_free(hdr);
1029 g_free(uri);
1030 g_free(to);
1033 static void do_register(struct sipe_account_data *sip)
1035 do_register_exp(sip, sip->registerexpire);
1038 static gchar *parse_from(const gchar *hdr)
1040 gchar *from;
1041 const gchar *tmp, *tmp2 = hdr;
1043 if (!hdr) return NULL;
1044 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1045 tmp = strchr(hdr, '<');
1047 /* i hate the different SIP UA behaviours... */
1048 if (tmp) { /* sip address in <...> */
1049 tmp2 = tmp + 1;
1050 tmp = strchr(tmp2, '>');
1051 if (tmp) {
1052 from = g_strndup(tmp2, tmp - tmp2);
1053 } else {
1054 purple_debug_info("sipe", "found < without > in From\n");
1055 return NULL;
1057 } else {
1058 tmp = strchr(tmp2, ';');
1059 if (tmp) {
1060 from = g_strndup(tmp2, tmp - tmp2);
1061 } else {
1062 from = g_strdup(tmp2);
1065 purple_debug_info("sipe", "got %s\n", from);
1066 return from;
1069 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1071 va_list args;
1072 xmlnode * node;
1073 const gchar * name;
1075 va_start(args, parent);
1076 while ((name = va_arg(args, const char *)) != NULL) {
1077 node = xmlnode_get_child(parent, name);
1078 if (node == NULL) return NULL;
1079 parent = node;
1081 va_end(args);
1083 return node;
1086 static void
1087 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1089 PurpleGroup * purple_group = purple_find_group(group->name);
1090 if (!purple_group) {
1091 purple_group = purple_group_new(group->name);
1092 purple_blist_add_group(purple_group, NULL);
1095 if (purple_group) {
1096 group->purple_group = purple_group;
1097 sip->groups = g_slist_append(sip->groups, group);
1098 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1099 } else {
1100 purple_debug_info("sipe", "did not add group %s\n", group->name);
1104 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1106 if (sip == NULL) {
1107 return NULL;
1110 struct sipe_group *group;
1111 GSList *entry = sip->groups;
1112 while (entry) {
1113 group = entry->data;
1114 if (group->id == id) {
1115 return group;
1117 entry = entry->next;
1119 return NULL;
1122 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1124 if (sip == NULL) {
1125 return NULL;
1128 struct sipe_group *group;
1129 GSList *entry = sip->groups;
1130 while (entry) {
1131 group = entry->data;
1132 if (!strcmp(group->name, name)) {
1133 return group;
1135 entry = entry->next;
1137 return NULL;
1140 static void
1141 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1143 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1144 gchar * body = g_strdup_printf(SIPE_SOAP_MOD_GROUP, group->id, name, sip->delta_num++);
1145 send_soap_request(sip, body);
1146 g_free(body);
1147 g_free(group->name);
1148 group->name = g_strdup(name);
1151 static void
1152 sipe_group_set_user (struct sipe_account_data *sip, struct sipe_group * group, const gchar * who)
1154 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1155 PurpleBuddy * purple_buddy = purple_find_buddy (sip->account, who);
1157 if (!group) {
1158 group = sipe_group_find_by_id (sip, buddy->group_id);
1160 buddy->group_id = group ? group->id : 1;
1162 if (buddy && purple_buddy) {
1163 gchar * alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1164 purple_debug_info("sipe", "Saving buddy %s with alias %s and group_id %d\n", who, alias, buddy->group_id);
1165 gchar * body = g_strdup_printf(SIPE_SOAP_SET_CONTACT,
1166 alias, buddy->group_id, "true", buddy->name, sip->delta_num++
1168 send_soap_request(sip, body);
1169 g_free(body);
1173 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1175 if (msg->response == 200) {
1176 struct sipe_group * group = g_new0(struct sipe_group, 1);
1178 struct group_user_context * ctx = (struct group_user_context*)tc->payload;
1179 group->name = ctx->group_name;
1181 xmlnode * xml = xmlnode_from_str(msg->body, msg->bodylen);
1182 if (!xml) return FALSE;
1184 xmlnode * node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1185 if (!node) return FALSE;
1187 char * group_id = xmlnode_get_data(node);
1188 if (!group_id) return FALSE;
1190 group->id = (int)g_ascii_strtod(group_id, NULL);
1192 sipe_group_add(sip, group);
1193 sipe_group_set_user(sip, group, ctx->user_name);
1195 g_free(ctx);
1196 xmlnode_free(xml);
1197 return TRUE;
1199 return FALSE;
1202 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1204 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1205 ctx->group_name = g_strdup(name);
1206 ctx->user_name = g_strdup(who);
1208 gchar * body = g_strdup_printf(SIPE_SOAP_ADD_GROUP, name, sip->delta_num++);
1209 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1210 g_free(body);
1213 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1215 gchar *to;
1217 if (msg->response == 200 || msg->response == 202) {
1218 return TRUE;
1221 to = parse_from(sipmsg_find_header(tc->msg, "To")); /* cant be NULL since it is our own msg */
1223 /* we can not subscribe -> user is offline (TODO unknown status?) */
1225 purple_prpl_got_user_status(sip->account, to, "offline", NULL);
1226 g_free(to);
1227 return TRUE;
1230 static void sipe_subscribe_to_name(struct sipe_account_data *sip, const char * buddy_name)
1232 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1233 gchar *tmp = get_contact(sip);
1234 gchar *contact = g_strdup_printf(
1235 "Accept: application/pidf+xml, application/xpidf+xml\r\n"
1236 "Event: presence\r\n"
1237 "Contact: %s\r\n", tmp);
1238 g_free(tmp);
1240 /* subscribe to buddy presence */
1241 send_sip_request(sip->gc, "SUBSCRIBE", to, to, contact, "", NULL, process_subscribe_response);
1243 g_free(to);
1244 g_free(contact);
1247 static void sipe_subscribe(struct sipe_account_data *sip, struct sipe_buddy *buddy)
1249 sipe_subscribe_to_name(sip, buddy->name);
1251 /* resubscribe before subscription expires */
1252 /* add some jitter */
1253 buddy->resubscribe = time(NULL)+1140+(rand()%50);
1256 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1258 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(purple_status_get_type(status));
1259 struct sipe_account_data *sip = NULL;
1261 if (!purple_status_is_active(status))
1262 return;
1264 if (account->gc)
1265 sip = account->gc->proto_data;
1267 if (sip) {
1268 g_free(sip->status);
1270 if (primitive == PURPLE_STATUS_AWAY) {
1271 sip->status = g_strdup("away");
1272 } else if (primitive == PURPLE_STATUS_AVAILABLE) {
1273 sip->status = g_strdup("available");
1274 } else if (primitive == PURPLE_STATUS_UNAVAILABLE) {
1275 sip->status = g_strdup("busy");
1278 send_presence_info(sip);
1282 static void
1283 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1285 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1286 sipe_group_set_user(sip, NULL, name);
1289 static void
1290 sipe_group_buddy(PurpleConnection *gc,
1291 const char *who,
1292 const char *old_group_name,
1293 const char *new_group_name)
1295 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1296 struct sipe_group * group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1297 if (!group) {
1298 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1299 } else {
1300 sipe_group_set_user(sip, group, who);
1304 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1306 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1307 struct sipe_buddy *b;
1309 // Prepend sip: if needed
1310 if (strncmp("sip:", buddy->name, 4)) {
1311 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1312 purple_blist_rename_buddy(buddy, buf);
1313 g_free(buf);
1316 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1317 b = g_new0(struct sipe_buddy, 1);
1318 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1319 b->name = g_strdup(buddy->name);
1320 g_hash_table_insert(sip->buddies, b->name, b);
1322 sipe_group_buddy(gc, b->name, NULL, group->name);
1323 } else {
1324 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1328 // Not Used; Remove?
1329 /*static void sipe_get_buddies(PurpleConnection *gc)
1331 PurpleBlistNode *gnode, *cnode, *bnode;
1333 purple_debug_info("sipe", "sipe_get_buddies\n");
1335 for (gnode = purple_get_blist()->root; gnode; gnode = gnode->next) {
1336 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) continue;
1337 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1338 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) continue;
1339 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1340 if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) continue;
1341 if (((PurpleBuddy*)bnode)->account == gc->account)
1342 sipe_add_buddy(gc, (PurpleBuddy*)bnode, (PurpleGroup *)gnode);
1348 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1350 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1351 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1353 if (!b) return;
1354 g_hash_table_remove(sip->buddies, buddy->name);
1356 if (b->name) {
1357 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->delta_num++);
1358 send_soap_request(sip, body);
1359 g_free(body);
1362 g_free(b->name);
1363 g_free(b);
1366 static void
1367 sipe_rename_group(PurpleConnection *gc,
1368 const char *old_name,
1369 PurpleGroup *group,
1370 GList *moved_buddies)
1372 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1373 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1374 if (group) {
1375 sipe_group_rename(sip, s_group, group->name);
1376 } else {
1377 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1381 static void
1382 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1384 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1385 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1386 if (s_group) {
1387 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1388 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->delta_num++);
1389 send_soap_request(sip, body);
1390 g_free(body);
1392 sip->groups = g_slist_remove(sip->groups, s_group);
1393 g_free(s_group->name);
1394 } else {
1395 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1399 static GList *sipe_status_types(PurpleAccount *acc)
1401 PurpleStatusType *type;
1402 GList *types = NULL;
1404 // Available
1405 type = purple_status_type_new_with_attrs(
1406 PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
1407 // Translators: noun
1408 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1409 NULL);
1410 types = g_list_append(types, type);
1412 // Away
1413 type = purple_status_type_new_with_attrs(
1414 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1415 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1416 NULL);
1417 types = g_list_append(types, type);
1419 // Busy
1420 type = purple_status_type_new_with_attrs(
1421 PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
1422 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1423 NULL);
1424 types = g_list_append(types, type);
1426 // Offline
1427 type = purple_status_type_new_full(
1428 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1429 types = g_list_append(types, type);
1431 return types;
1434 static gboolean sipe_add_lcs_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1436 int len = msg->bodylen;
1438 gchar *tmp = sipmsg_find_header(msg, "Event");
1439 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1440 return FALSE;
1443 /* Convert the contact from XML to Purple Buddies */
1444 xmlnode * isc = xmlnode_from_str(msg->body, len);
1445 if (!isc) {
1446 return FALSE;
1449 gchar * delta_num = g_strdup(xmlnode_get_attrib(isc, "deltaNum"));
1450 if (delta_num) {
1451 sip->delta_num = (int)g_ascii_strtod(delta_num, NULL);
1454 /* Parse groups */
1455 xmlnode *group_node;
1456 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1457 struct sipe_group * group = g_new0(struct sipe_group, 1);
1459 group->name = g_strdup(xmlnode_get_attrib(group_node, "name"));
1460 if (!strncmp(group->name, "~", 1)){
1461 // TODO translate
1462 group->name = "General";
1464 group->name = g_strdup(group->name);
1465 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1467 sipe_group_add(sip, group);
1470 // Make sure we have at least one group
1471 if (g_slist_length(sip->groups) == 0) {
1472 struct sipe_group * group = g_new0(struct sipe_group, 1);
1473 // TODO translate
1474 group->name = g_strdup("General");
1475 group->id = 1;
1476 PurpleGroup * purple_group = purple_group_new(group->name);
1477 purple_blist_add_group(purple_group, NULL);
1478 sip->groups = g_slist_append(sip->groups, group);
1481 /* Parse contacts */
1482 xmlnode *item;
1483 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1484 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1485 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1486 gchar **item_groups = g_strsplit(xmlnode_get_attrib(item, "groups"), " ", 0);
1488 struct sipe_group * group = NULL;
1490 // Find the first group this contact belongs to; that's where we'll place it in the buddy list
1491 if (item_groups[0]) {
1492 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[0], NULL));
1495 // If couldn't find the right group for this contact, just put them in the first group we have
1496 if (group == NULL && g_slist_length(sip->groups) > 0) {
1497 group = sip->groups->data;
1500 if (group != NULL) {
1501 char * buddy_name = g_strdup_printf("sip:%s", uri);
1503 //b = purple_find_buddy(sip->account, buddy_name);
1504 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1505 if (!b){
1506 b = purple_buddy_new(sip->account, buddy_name, uri);
1508 g_free(buddy_name);
1510 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1512 if (name != NULL && strlen(name) != 0) {
1513 purple_blist_alias_buddy(b, name);
1514 } else {
1515 purple_blist_alias_buddy(b, uri);
1518 struct sipe_buddy * buddy = g_new0(struct sipe_buddy, 1);
1519 buddy->name = g_strdup(b->name);
1520 buddy->group_id = group->id;
1521 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1523 purple_debug_info("sipe", "Added buddy %s to group %s\n", buddy->name, group->name);
1524 } else {
1525 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1526 name);
1530 xmlnode_free(isc);
1532 return 0;
1535 static void sipe_subscribe_buddylist(struct sipe_account_data *sip,struct sipmsg *msg)
1537 gchar *to = g_strdup_printf("sip:%s", sip->username);
1538 gchar *tmp = get_contact(sip);
1539 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-roaming-contacts\r\n"
1540 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
1541 "Supported: com.microsoft.autoextend\r\n"
1542 "Supported: ms-benotify\r\n"
1543 "Proxy-Require: ms-benotify\r\n"
1544 "Supported: ms-piggyback-first-notify\r\n"
1545 "Contact: %s\r\n", tmp);
1546 g_free(tmp);
1548 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, sipe_add_lcs_contacts);
1549 g_free(to);
1550 g_free(hdr);
1553 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
1555 gchar *to = g_strdup_printf("sip:%s", sip->username);
1556 gchar *tmp = get_contact(sip);
1557 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-roaming-self\r\n"
1558 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
1559 "Supported: com.microsoft.autoextend\r\n"
1560 "Supported: ms-benotify\r\n"
1561 "Proxy-Require: ms-benotify\r\n"
1562 "Supported: ms-piggyback-first-notify\r\n"
1563 "Contact: %s\r\n"
1564 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n"
1565 ,tmp);
1567 g_free(tmp);
1569 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>");
1571 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
1572 g_free(body);
1573 g_free(to);
1574 g_free(hdr);
1577 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
1579 gchar *to = g_strdup_printf("sip:%s", sip->username);
1580 gchar *tmp = get_contact(sip);
1581 gchar *hdr = g_strdup_printf("Event: vnd-microsoft-provisioning-v2\r\n"
1582 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
1583 "Supported: com.microsoft.autoextend\r\n"
1584 "Supported: ms-benotify\r\n"
1585 "Proxy-Require: ms-benotify\r\n"
1586 "Supported: ms-piggyback-first-notify\r\n"
1587 "Expires: 0\r\n"
1588 "Contact: %s\r\n"
1589 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
1590 ,tmp);
1592 g_free(tmp);
1594 gchar *body=g_strdup("<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\"><provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/><provisioningGroup name=\"ucPolicy\"/></provisioningGroupList>");
1595 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, NULL);
1596 g_free(body);
1597 g_free(to);
1598 g_free(hdr);
1601 /* IM Session (INVITE and MESSAGE methods) */
1603 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
1605 if (sip == NULL || who == NULL) {
1606 return NULL;
1609 struct sip_im_session *session;
1610 GSList *entry = sip->im_sessions;
1611 while (entry) {
1612 session = entry->data;
1613 if ((who != NULL && !strcmp(who, session->with))) {
1614 return session;
1616 entry = entry->next;
1618 return NULL;
1621 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
1623 struct sip_im_session *session = find_im_session(sip, who);
1624 if (!session) {
1625 session = g_new0(struct sip_im_session, 1);
1626 session->with = g_strdup(who);
1627 sip->im_sessions = g_slist_append(sip->im_sessions, session);
1629 return session;
1632 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
1634 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
1635 // TODO free session resources
1638 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
1640 gchar *hdr;
1641 gchar *fullto;
1642 gchar *tmp;
1644 if (strncmp("sip:", session->with, 4)) {
1645 fullto = g_strdup_printf("sip:%s", session->with);
1646 } else {
1647 fullto = g_strdup(session->with);
1650 hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
1651 //hdr = g_strdup("Content-Type: text/rtf\r\n");
1652 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
1654 tmp = get_contact(sip);
1655 hdr = g_strdup_printf("Contact: %s\r\n%s", tmp, hdr);
1656 g_free(tmp);
1658 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msg, session->dialog, NULL);
1660 g_free(hdr);
1661 g_free(fullto);
1665 static void
1666 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
1668 GSList *entry = session->outgoing_message_queue;
1669 while (entry) {
1670 char *queued_msg = entry->data;
1671 sipe_send_message(sip, session, queued_msg);
1673 // Remove from the queue and free the string
1674 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
1675 g_free(queued_msg);
1679 static void
1680 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
1682 GSList *hdr = msg->headers;
1683 struct siphdrelement *elem;
1684 gchar *contact;
1686 while(hdr)
1688 elem = hdr->data;
1689 if(!strcmp(elem->name, "Record-Route"))
1691 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
1692 dialog->routes = g_slist_append(dialog->routes, route);
1694 hdr = g_slist_next(hdr);
1697 if (outgoing)
1699 dialog->routes = g_slist_reverse(dialog->routes);
1702 if (dialog->routes)
1704 dialog->request = dialog->routes->data;
1705 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
1708 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
1709 dialog->routes = g_slist_append(dialog->routes, contact);
1713 static void
1714 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
1716 gchar *us = outgoing ? "From" : "To";
1717 gchar *them = outgoing ? "To" : "From";
1719 dialog->callid = sipmsg_find_header(msg, "Call-ID");
1720 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
1721 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
1722 if (!dialog->theirepid) {
1723 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
1725 if (!dialog->theirepid) {
1726 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
1729 sipe_get_route_header(msg, dialog, outgoing);
1733 static gboolean
1734 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
1736 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
1737 struct sip_im_session * session = find_im_session(sip, with);
1738 g_free(with);
1740 if (!session) {
1741 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
1742 return FALSE;
1745 if (msg->response != 200) {
1746 purple_debug_info("sipe", "process_invite_response: INVITE response not 200, ignoring\n");
1747 im_session_destroy(sip, session);
1748 return FALSE;
1751 struct sip_dialog * dialog = session->dialog;
1752 if (!dialog) {
1753 purple_debug_info("sipe", "process_invite_response: session outgoign dialog is NULL\n");
1754 return FALSE;
1757 sipe_parse_dialog(msg, dialog, TRUE);
1758 dialog->cseq = 0;
1760 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
1761 session->outgoing_invite = NULL;
1762 sipe_im_process_queue(sip, session);
1764 return TRUE;
1768 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session * session, gchar * msg_body)
1770 gchar *hdr;
1771 gchar *to;
1772 gchar *contact;
1773 gchar *body;
1775 if (session->dialog) {
1776 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
1777 return;
1780 session->dialog = g_new0(struct sip_dialog, 1);
1782 if (strstr(session->with, "sip:")) {
1783 to = g_strdup(session->with);
1784 } else {
1785 to = g_strdup_printf("sip:%s", session->with);
1788 char * base64_msg = purple_base64_encode((guchar*) msg_body, strlen(msg_body));
1789 char * ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, base64_msg);
1790 g_free(base64_msg);
1792 contact = get_contact(sip);
1793 hdr = g_strdup_printf(
1794 "Contact: %s\r\n%s"
1795 "Content-Type: application/sdp\r\n",
1796 contact, ms_text_format, sip->username, sip->username, to);
1797 g_free(ms_text_format);
1799 body = g_strdup_printf(
1800 "v=0\r\n"
1801 "o=- 0 0 IN IP4 %s\r\n"
1802 "s=session\r\n"
1803 "c=IN IP4 %s\r\n"
1804 "t=0 0\r\n"
1805 "m=message %d sip null\r\n"
1806 "a=accept-types:text/plain text/html image/gif "
1807 "multipart/alternative application/im-iscomposing+xml\r\n",
1808 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
1810 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
1811 to, to, hdr, body, session->dialog, process_invite_response);
1813 g_free(to);
1814 g_free(body);
1815 g_free(hdr);
1816 g_free(contact);
1819 static void
1820 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
1822 if (session) {
1823 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
1824 im_session_destroy(sip, session);
1828 static void
1829 sipe_convo_closed(PurpleConnection * gc, const char *who)
1831 struct sipe_account_data *sip = gc->proto_data;
1833 purple_debug_info("sipe", "conversation with %s closed\n", who);
1834 im_session_close(sip, find_im_session(sip, who));
1837 static void
1838 im_session_close_all (struct sipe_account_data *sip)
1840 GSList *entry = sip->im_sessions;
1841 while (entry) {
1842 im_session_close (sip, entry->data);
1843 entry = sip->im_sessions;
1847 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
1849 struct sipe_account_data *sip = gc->proto_data;
1850 char *to = g_strdup(who);
1851 char *text = purple_unescape_html(what);
1853 struct sip_im_session * session = find_or_create_im_session(sip, who);
1855 // Queue the message
1856 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, text);
1858 if (session->dialog && session->dialog->callid) {
1859 sipe_im_process_queue(sip, session);
1860 } else if (!session->outgoing_invite) {
1861 // Need to send the INVITE to get the outgoing dialog setup
1862 sipe_invite(sip, session, text);
1865 g_free(to);
1866 return 1;
1870 /* End IM Session (INVITE and MESSAGE methods) */
1872 static unsigned int
1873 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
1875 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1877 if (state == PURPLE_NOT_TYPING)
1878 return 0;
1880 struct sip_im_session * session = find_im_session(sip, who);
1882 if (session && session->dialog) {
1883 send_sip_request(gc, "INFO", who, who,
1884 "Content-Type: application/xml\r\n",
1885 SIPE_SEND_TYPING, session->dialog, NULL);
1888 return SIPE_TYPING_SEND_TIMEOUT;
1892 static void sipe_buddy_resub(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1894 time_t curtime = time(NULL);
1895 if (buddy->resubscribe < curtime) {
1896 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_buddy_resub %s\n", name);
1897 sipe_subscribe(sip, buddy);
1901 static gboolean resend_timeout(struct sipe_account_data *sip)
1903 GSList *tmp = sip->transactions;
1904 time_t currtime = time(NULL);
1905 while (tmp) {
1906 struct transaction *trans = tmp->data;
1907 tmp = tmp->next;
1908 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
1909 if ((currtime - trans->time > 5) && trans->retries >= 1) {
1910 /* TODO 408 */
1911 } else {
1912 if ((currtime - trans->time > 2) && trans->retries == 0) {
1913 trans->retries++;
1914 sendout_sipmsg(sip, trans->msg);
1918 return TRUE;
1921 static gboolean subscribe_timeout(struct sipe_account_data *sip)
1923 GSList *tmp;
1924 time_t curtime = time(NULL);
1925 /* register again if first registration expires */
1926 if (sip->reregister < curtime) {
1927 do_register(sip);
1929 /* check for every subscription if we need to resubscribe */
1930 //Fixxxer we need resub?
1931 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_resub, (gpointer)sip);
1933 /* remove a timed out suscriber */
1935 tmp = sip->watcher;
1936 while (tmp) {
1937 struct sipe_watcher *watcher = tmp->data;
1938 if (watcher->expire < curtime) {
1939 watcher_remove(sip, watcher->name);
1940 tmp = sip->watcher;
1942 if (tmp) tmp = tmp->next;
1945 return TRUE;
1948 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
1950 gchar *from;
1951 gchar *contenttype;
1952 gboolean found = FALSE;
1954 from = parse_from(sipmsg_find_header(msg, "From"));
1956 if (!from) return;
1958 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
1960 contenttype = sipmsg_find_header(msg, "Content-Type");
1961 if (!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
1962 serv_got_im(sip->gc, from, msg->body, 0, time(NULL));
1963 send_sip_response(sip->gc, msg, 200, "OK", NULL);
1964 found = TRUE;
1966 if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
1967 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
1968 xmlnode *state;
1969 gchar *statedata;
1971 if (!isc) {
1972 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
1973 return;
1976 state = xmlnode_get_child(isc, "state");
1978 if (!state) {
1979 purple_debug_info("sipe", "process_incoming_message: no state found\n");
1980 xmlnode_free(isc);
1981 return;
1984 statedata = xmlnode_get_data(state);
1985 if (statedata) {
1986 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
1987 else serv_got_typing_stopped(sip->gc, from);
1989 g_free(statedata);
1991 xmlnode_free(isc);
1992 send_sip_response(sip->gc, msg, 200, "OK", NULL);
1993 found = TRUE;
1995 if (!found) {
1996 purple_debug_info("sipe", "got unknown mime-type");
1997 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
1999 g_free(from);
2002 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2004 // Only accept text invitations
2005 if (msg->body && !strstr(msg->body, "m=message")) {
2006 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2007 return;
2010 gchar * from = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "<", ">", NULL);
2011 struct sip_im_session * session = find_or_create_im_session (sip, from);
2012 if (session) {
2013 if (session->dialog) {
2014 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2015 } else {
2016 session->dialog = g_new0(struct sip_dialog, 1);
2018 sipe_parse_dialog(msg, session->dialog, FALSE);
2020 session->dialog->callid = sipmsg_find_header(msg, "Call-ID");
2021 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
2022 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
2023 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
2025 } else {
2026 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2028 g_free(from);
2030 send_sip_response(sip->gc, msg, 200, "OK", g_strdup_printf(
2031 "v=0\r\n"
2032 "o=- 0 0 IN IP4 %s\r\n"
2033 "s=session\r\n"
2034 "c=IN IP4 %s\r\n"
2035 "t=0 0\r\n"
2036 "m=message %d sip sip:%s\r\n"
2037 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2038 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2039 sip->realport, sip->username));
2042 static void sipe_connection_cleanup(struct sipe_account_data *);
2043 static void create_connection(struct sipe_account_data *, gchar *, int);
2045 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2047 gchar *tmp, krb5_token;
2048 const gchar *expires_header;
2049 int expires;
2051 expires_header = sipmsg_find_header(msg, "Expires");
2052 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
2053 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
2055 switch (msg->response) {
2056 case 200:
2057 if (expires == 0) {
2058 sip->registerstatus = 0;
2059 } else {
2060 sip->registerstatus = 3;
2061 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
2063 gchar *gruu = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "gruu=\"", "\"", NULL);
2064 if(gruu) {
2065 sip->contact = g_strdup_printf("<%s>", gruu);
2066 } else {
2067 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);
2070 /* get buddies from blist; Has a bug */
2071 subscribe_timeout(sip);
2073 tmp = sipmsg_find_header(msg, "Allow-Events");
2074 if (tmp && strstr(tmp, "vnd-microsoft-provisioning")){
2075 sipe_subscribe_buddylist(sip, msg);
2078 sipe_subscribe_roaming_self(sip, msg);
2079 sipe_subscribe_roaming_provisioning(sip, msg);
2080 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
2082 // Should we remove the transaction here?
2083 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
2084 transactions_remove(sip, tc);
2086 break;
2087 case 301:
2089 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
2091 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
2092 gchar **parts = g_strsplit(redirect + 4, ";", 0);
2093 gchar **tmp;
2094 gchar *hostname;
2095 int port = 0;
2096 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
2097 int i = 1;
2099 tmp = g_strsplit(parts[0], ":", 0);
2100 hostname = g_strdup(tmp[0]);
2101 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
2102 g_strfreev(tmp);
2104 while (parts[i]) {
2105 tmp = g_strsplit(parts[i], "=", 0);
2106 if (tmp[1]) {
2107 if (g_strcasecmp("transport", tmp[0]) == 0) {
2108 if (g_strcasecmp("tcp", tmp[1]) == 0) {
2109 transport = SIPE_TRANSPORT_TCP;
2110 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
2111 transport = SIPE_TRANSPORT_UDP;
2115 g_strfreev(tmp);
2116 i++;
2118 g_strfreev(parts);
2120 /* Close old connection */
2121 sipe_connection_cleanup(sip);
2123 /* Create new connection */
2124 sip->transport = transport;
2125 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
2126 hostname, port, TRANSPORT_DESCRIPTOR);
2127 create_connection(sip, hostname, port);
2130 break;
2131 case 401:
2132 if (sip->registerstatus != 2) {
2133 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
2134 if (sip->registrar.retries > 3) {
2135 sip->gc->wants_to_die = TRUE;
2136 purple_connection_error(sip->gc, _("Wrong Password"));
2137 return TRUE;
2139 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
2140 tmp = sipmsg_find_auth_header(msg, "NTLM");
2141 } else {
2142 tmp = sipmsg_find_auth_header(msg, "Kerberos");
2144 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
2145 fill_auth(sip, tmp, &sip->registrar);
2146 sip->registerstatus = 2;
2147 if (sip->account->disconnecting) {
2148 do_register_exp(sip, 0);
2149 } else {
2150 do_register(sip);
2153 break;
2154 case 403:
2156 const gchar *warning = sipmsg_find_header(msg, "Warning");
2157 if (warning != NULL) {
2158 /* Example header:
2159 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
2161 gchar **tmp = g_strsplit(warning, "\"", 0);
2162 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
2163 g_strfreev(tmp);
2164 } else {
2165 warning = _("You have been rejected by the server");
2168 sip->gc->wants_to_die = TRUE;
2169 purple_connection_error(sip->gc, warning);
2170 return TRUE;
2172 break;
2173 case 503:
2175 const gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2176 if (warning != NULL) {
2177 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2178 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
2179 g_free(reason);
2180 } else {
2181 warning = _("Service unavailable: no reason given");
2184 sip->gc->wants_to_die = TRUE;
2185 purple_connection_error(sip->gc, warning);
2186 return TRUE;
2188 break;
2190 return TRUE;
2193 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2195 gchar *from;
2196 gchar *fromhdr;
2197 gchar *tmp2;
2198 gchar *activity = g_strdup("available");
2199 xmlnode *pidf;
2200 xmlnode *basicstatus = NULL, *tuple, *status;
2201 gboolean isonline = FALSE;
2203 fromhdr = sipmsg_find_header(msg, "From");
2204 from = parse_from(fromhdr);
2206 if (!from) {
2207 return;
2210 pidf = xmlnode_from_str(msg->body, msg->bodylen);
2211 if (!pidf) {
2212 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf\n");
2213 return;
2216 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
2217 if ((status = xmlnode_get_child(tuple, "status"))) {
2218 basicstatus = xmlnode_get_child(status, "basic");
2222 if (!basicstatus) {
2223 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
2224 xmlnode_free(pidf);
2225 return;
2228 tmp2 = xmlnode_get_data(basicstatus);
2229 if (!tmp2) {
2230 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
2231 xmlnode_free(pidf);
2232 return;
2235 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", tmp2);
2236 if (strstr(tmp2, "open")) {
2237 isonline = TRUE;
2240 xmlnode *display_name_node = xmlnode_get_child(pidf, "display-name");
2241 if (display_name_node) {
2242 PurpleBuddy * buddy = purple_find_buddy (sip->account, from);
2243 char * display_name = xmlnode_get_data(display_name_node);
2244 if (buddy && display_name) {
2245 purple_blist_server_alias_buddy (buddy, g_strdup(display_name));
2249 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
2250 if ((status = xmlnode_get_child(tuple, "status"))) {
2251 if (basicstatus = xmlnode_get_child(status, "activities")) {
2252 if (basicstatus = xmlnode_get_child(basicstatus, "activity")) {
2253 activity = xmlnode_get_data(basicstatus);
2258 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
2260 if (isonline) {
2261 gchar * status_id = NULL;
2262 if (activity) {
2263 if (strstr(activity, "busy")) {
2264 status_id = "busy";
2265 } else if (strstr(activity, "away")) {
2266 status_id = "away";
2270 if (!status_id) {
2271 status_id = "available";
2274 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
2275 purple_prpl_got_user_status(sip->account, from, status_id, NULL);
2276 } else {
2277 purple_prpl_got_user_status(sip->account, from, "offline", NULL);
2280 xmlnode_free(pidf);
2281 g_free(from);
2282 g_free(tmp2);
2283 g_free(activity);
2285 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2288 static void process_incoming_benotify(struct sipe_account_data *sip, struct sipmsg *msg)
2290 xmlnode *xml = xmlnode_from_str(msg->body, msg->bodylen);
2291 if (!xml) return;
2293 gchar * delta_num = g_strdup(xmlnode_get_attrib(xml, "deltaNum"));
2294 if (delta_num) {
2295 sip->delta_num = (int)g_ascii_strtod(delta_num, NULL);
2298 xmlnode_free(xml);
2302 static gchar* gen_xpidf(struct sipe_account_data *sip)
2304 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
2305 "<presence>\r\n"
2306 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
2307 "<display name=\"sip:%s\"/>\r\n"
2308 "<atom id=\"1234\">\r\n"
2309 "<address uri=\"sip:%s\">\r\n"
2310 "<status status=\"%s\"/>\r\n"
2311 "</address>\r\n"
2312 "</atom>\r\n"
2313 "</presence>\r\n",
2314 sip->username,
2315 sip->username,
2316 sip->username,
2317 sip->status);
2318 return doc;
2323 static gchar* gen_pidf(struct sipe_account_data *sip)
2325 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
2326 "<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"
2327 "<tuple id=\"0\">\r\n"
2328 "<status>\r\n"
2329 "<basic>open</basic>\r\n"
2330 "<ep:activities>\r\n"
2331 " <ep:activity>%s</ep:activity>\r\n"
2332 "</ep:activities>"
2333 "</status>\r\n"
2334 "</tuple>\r\n"
2335 "<ci:display-name>%s</ci:display-name>\r\n"
2336 "</presence>",
2337 sip->username,
2338 sip->status,
2339 sip->username);
2340 return doc;
2343 static void send_notify(struct sipe_account_data *sip, struct sipe_watcher *watcher)
2345 gchar *doc = watcher->needsxpidf ? gen_xpidf(sip) : gen_pidf(sip);
2346 gchar *hdr = watcher->needsxpidf ? "Event: presence\r\nContent-Type: application/xpidf+xml\r\n" : "Event: presence\r\nContent-Type: application/pidf+xml\r\n";
2347 send_sip_request(sip->gc, "NOTIFY", watcher->name, watcher->name, hdr, doc, &watcher->dialog, NULL);
2348 g_free(doc);
2351 static gboolean process_service_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2354 if (msg->response != 200 && msg->response != 408) {
2355 /* never send again */
2356 sip->republish = -1;
2358 return TRUE;
2361 static void send_clear_notes(struct sipe_account_data *sip)
2365 static gboolean
2366 process_send_presence_info_v0_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2368 if (msg->response == 488) {
2369 sip->presence_method_version = 1;
2370 send_presence_info(sip);
2372 return TRUE;
2375 static void send_presence_info_v0(struct sipe_account_data *sip, char * note)
2377 int code;
2378 if (!strcmp(sip->status, "away")) {
2379 code = 100;
2380 } else if (!strcmp(sip->status, "busy")) {
2381 code = 600;
2382 } else {
2383 // Available
2384 code = 400;
2387 gchar *name = g_strdup_printf("sip: sip:%s", sip->username);
2388 gchar * body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE, name, 200, code, note);
2389 send_soap_request_with_cb(sip, body, process_send_presence_info_v0_response, NULL);
2390 g_free(name);
2391 g_free(body);
2394 static gboolean
2395 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2397 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
2398 if (msg->response == 200) {
2399 sip->status_version = 0;
2400 send_presence_info(sip);
2402 return TRUE;
2405 static gboolean
2406 process_send_presence_info_v1_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
2408 if (msg->response == 409) {
2409 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
2410 // TODO need to parse the version #'s?
2411 gchar *uri = g_strdup_printf("sip:%s", sip->username);
2412 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
2414 gchar *tmp = get_contact(sip);
2415 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
2416 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
2418 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
2420 g_free(tmp);
2421 g_free(hdr);
2422 g_free(uri);
2423 g_free(doc);
2425 return TRUE;
2428 static void send_presence_info_v1(struct sipe_account_data *sip, char * note)
2430 int code;
2431 if (!strcmp(sip->status, "away")) {
2432 code = 12000;
2433 } else if (!strcmp(sip->status, "busy")) {
2434 code = 6000;
2435 } else {
2436 // Available
2437 code = 3000;
2440 gchar *uri = g_strdup_printf("sip:%s", sip->username);
2441 gchar *doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
2442 sip->status_version, code,
2443 sip->status_version, code,
2444 sip->status_version, note ? note : "",
2445 sip->status_version, note ? note : "",
2446 sip->status_version, note ? note : ""
2448 sip->status_version++;
2450 gchar *tmp = get_contact(sip);
2451 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
2452 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
2454 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_info_v1_response);
2456 g_free(tmp);
2457 g_free(hdr);
2458 g_free(uri);
2459 g_free(doc);
2462 static void send_presence_info(struct sipe_account_data *sip)
2464 PurpleStatus * status = purple_account_get_active_status(sip->account);
2465 if (!status) return;
2467 gchar *note = g_strdup(purple_status_get_attr_string(status, "message"));
2469 purple_debug_info("sipe", "sending presence info, version = %d\n", sip->presence_method_version);
2470 if (sip->presence_method_version != 1) {
2471 send_presence_info_v0(sip, note);
2472 } else {
2473 send_presence_info_v1(sip, note);
2477 static void send_service(struct sipe_account_data *sip)
2479 //gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->sipdomain);
2480 gchar *uri = g_strdup_printf("sip:%s", sip->username);
2481 //gchar *doc = gen_pidf(sip);
2483 gchar *doc = gen_pidf(sip);
2484 gchar *hdr = g_strdup("Event: presence\r\nContent-Type: application/pidf+xml\r\n");
2486 //gchar *hdr = g_strdup("Content-Type: application/SOAP+xml\r\n");
2487 gchar *tmp = get_contact(sip);
2488 hdr = g_strdup_printf("Contact: %s\r\n%s; +sip.instance=\"<urn:uuid:%s>\"", tmp, hdr,generateUUIDfromEPID(get_epid()));
2489 g_free(tmp);
2490 send_sip_request(sip->gc, "SERVICE", uri, uri,
2491 hdr,
2492 doc, NULL, process_service_response);
2493 sip->republish = time(NULL) + 500;
2494 g_free(hdr);
2495 g_free(uri);
2496 g_free(doc);
2499 static void process_incoming_subscribe(struct sipe_account_data *sip, struct sipmsg *msg)
2501 const char *from_hdr = sipmsg_find_header(msg, "From");
2502 gchar *from = parse_from(from_hdr);
2503 gchar *theirtag = find_tag(from_hdr);
2504 gchar *ourtag = find_tag(sipmsg_find_header(msg, "To"));
2505 gboolean tagadded = FALSE;
2506 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2507 gchar *expire = sipmsg_find_header(msg, "Expire");
2508 gchar *tmp;
2509 struct sipe_watcher *watcher = watcher_find(sip, from);
2510 if (!ourtag) {
2511 tagadded = TRUE;
2512 ourtag = gentag();
2514 if (!watcher) { /* new subscription */
2515 gchar *acceptheader = sipmsg_find_header(msg, "Accept");
2516 gboolean needsxpidf = FALSE;
2517 if (!purple_privacy_check(sip->account, from)) {
2518 send_sip_response(sip->gc, msg, 202, "Ok", NULL);
2519 goto privend;
2521 if (acceptheader) {
2522 gchar *tmp = acceptheader;
2523 gboolean foundpidf = FALSE;
2524 gboolean foundxpidf = FALSE;
2525 while (tmp && tmp < acceptheader + strlen(acceptheader)) {
2526 gchar *tmp2 = strchr(tmp, ',');
2527 if (tmp2) *tmp2 = '\0';
2528 if (!strcmp("application/pidf+xml", tmp))
2529 foundpidf = TRUE;
2530 if (!strcmp("application/xpidf+xml", tmp))
2531 foundxpidf = TRUE;
2532 if (tmp2) {
2533 *tmp2 = ',';
2534 tmp = tmp2;
2535 while (*tmp == ' ') tmp++;
2536 } else
2537 tmp = 0;
2539 if (!foundpidf && foundxpidf) needsxpidf = TRUE;
2540 g_free(acceptheader);
2542 watcher = watcher_create(sip, from, callid, ourtag, theirtag, needsxpidf);
2544 if (tagadded) {
2545 gchar *to = g_strdup_printf("%s;tag=%s", sipmsg_find_header(msg, "To"), ourtag);
2546 sipmsg_remove_header(msg, "To");
2547 sipmsg_add_header(msg, "To", to);
2548 g_free(to);
2550 if (expire)
2551 watcher->expire = time(NULL) + strtol(expire, NULL, 10);
2552 else
2553 watcher->expire = time(NULL) + 600;
2554 //Fixxxer
2555 sipmsg_remove_header(msg, "Contact");
2556 tmp = get_contact(sip);
2557 sipmsg_add_header(msg, "Contact", tmp);
2558 g_free(tmp);
2559 purple_debug_info("sipe", "got subscribe: name %s ourtag %s theirtag %s callid %s\n", watcher->name, watcher->dialog.ourtag, watcher->dialog.theirtag, watcher->dialog.callid);
2560 send_sip_response(sip->gc, msg, 200, "Ok", NULL);
2561 send_notify(sip, watcher);
2562 privend:
2563 g_free(from);
2564 g_free(theirtag);
2565 g_free(ourtag);
2566 g_free(callid);
2567 g_free(expire);
2570 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
2572 gboolean found = FALSE;
2573 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
2574 if (msg->response == 0) { /* request */
2575 if (!strcmp(msg->method, "MESSAGE")) {
2576 process_incoming_message(sip, msg);
2577 found = TRUE;
2578 } else if (!strcmp(msg->method, "NOTIFY")) {
2579 purple_debug_info("sipe","send->process_incoming_notify\n");
2580 process_incoming_notify(sip, msg);
2581 found = TRUE;
2582 } else if (!strcmp(msg->method, "BENOTIFY")) {
2583 purple_debug_info("sipe","send->process_incoming_benotify\n");
2584 process_incoming_benotify(sip, msg);
2585 found = TRUE;
2586 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
2587 purple_debug_info("sipe","send->process_incoming_subscribe\n");
2588 process_incoming_subscribe(sip, msg);
2589 found = TRUE;
2590 } else if (!strcmp(msg->method, "INVITE")) {
2591 process_incoming_invite(sip, msg);
2592 found = TRUE;
2593 } else if (!strcmp(msg->method, "INFO")) {
2594 // TODO needs work
2595 gchar * from = parse_from(sipmsg_find_header(msg, "From"));
2596 if (from) {
2597 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
2599 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2600 found = TRUE;
2601 } else if (!strcmp(msg->method, "ACK")) {
2602 // ACK's don't need any response
2603 found = TRUE;
2604 } else if (!strcmp(msg->method, "BYE")) {
2605 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2607 gchar * from = parse_from(sipmsg_find_header(msg, "From"));
2608 struct sip_im_session * session = find_im_session (sip, from);
2609 g_free(from);
2611 if (session) {
2612 // TODO Let the user know the other user left the conversation?
2613 im_session_destroy(sip, session);
2616 found = TRUE;
2617 } else {
2618 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
2620 } else { /* response */
2621 struct transaction *trans = transactions_find(sip, msg);
2622 if (trans) {
2623 if (msg->response == 407) {
2624 gchar *resend, *auth, *ptmp;
2626 if (sip->proxy.retries > 30) return;
2627 sip->proxy.retries++;
2628 /* do proxy authentication */
2630 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
2632 fill_auth(sip, ptmp, &sip->proxy);
2633 auth = auth_header(sip, &sip->proxy, trans->msg, TRUE);
2634 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
2635 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
2636 g_free(auth);
2637 resend = sipmsg_to_string(trans->msg);
2638 /* resend request */
2639 sendout_pkt(sip->gc, resend);
2640 g_free(resend);
2641 } else {
2642 if (msg->response == 100 || msg->response == 180) {
2643 /* ignore provisional response */
2644 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
2645 } else {
2646 sip->proxy.retries = 0;
2647 if (!strcmp(trans->msg->method, "REGISTER")) {
2648 if (msg->response == 401) sip->registrar.retries++;
2649 else sip->registrar.retries = 0;
2650 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
2651 } else {
2652 if (msg->response == 401) {
2653 gchar *resend, *auth, *ptmp;
2655 if (sip->registrar.retries > 4) return;
2656 sip->registrar.retries++;
2658 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
2659 ptmp = sipmsg_find_auth_header(msg, "NTLM");
2660 } else {
2661 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
2664 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
2666 fill_auth(sip, ptmp, &sip->registrar);
2667 auth = auth_header(sip, &sip->registrar, trans->msg, TRUE);
2668 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
2669 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
2671 //sipmsg_remove_header(trans->msg, "Authorization");
2672 //sipmsg_add_header(trans->msg, "Authorization", auth);
2673 g_free(auth);
2674 resend = sipmsg_to_string(trans->msg);
2675 /* resend request */
2676 sendout_pkt(sip->gc, resend);
2677 g_free(resend);
2681 if (trans->callback) {
2682 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
2683 /* call the callback to process response*/
2684 (trans->callback)(sip, msg, trans);
2686 /* Not sure if this is needed or what needs to be done
2687 but transactions seem to be removed prematurely so
2688 this only removes them if the response is 200 OK */
2689 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
2690 /*Has a bug and it's unneccesary*/
2691 /*transactions_remove(sip, trans);*/
2695 found = TRUE;
2696 } else {
2697 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction");
2700 if (!found) {
2701 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
2705 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
2707 char *cur;
2708 char *dummy;
2709 struct sipmsg *msg;
2710 int restlen;
2711 cur = conn->inbuf;
2713 /* according to the RFC remove CRLF at the beginning */
2714 while (*cur == '\r' || *cur == '\n') {
2715 cur++;
2717 if (cur != conn->inbuf) {
2718 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
2719 conn->inbufused = strlen(conn->inbuf);
2722 /* Received a full Header? */
2723 while ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
2724 time_t currtime = time(NULL);
2725 cur += 2;
2726 cur[0] = '\0';
2727 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
2728 msg = sipmsg_parse_header(conn->inbuf);
2729 cur[0] = '\r';
2730 cur += 2;
2731 restlen = conn->inbufused - (cur - conn->inbuf);
2732 if (restlen >= msg->bodylen) {
2733 dummy = g_malloc(msg->bodylen + 1);
2734 memcpy(dummy, cur, msg->bodylen);
2735 dummy[msg->bodylen] = '\0';
2736 msg->body = dummy;
2737 cur += msg->bodylen;
2738 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
2739 conn->inbufused = strlen(conn->inbuf);
2740 } else {
2741 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
2742 restlen, msg->bodylen, strlen(conn->inbuf));
2743 sipmsg_free(msg);
2744 return;
2747 if (msg->body) {
2748 purple_debug_info("sipe", "body:\n%s", msg->body);
2751 // Verify the signature before processing it
2752 if (sip->registrar.ntlm_key) {
2753 struct sipmsg_breakdown msgbd;
2754 msgbd.msg = msg;
2755 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
2756 gchar * signature_input_str = sipmsg_breakdown_get_string(&msgbd);
2757 gchar * signature;
2758 if (signature_input_str != NULL) {
2759 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
2762 gchar * rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
2764 if (signature != NULL) {
2765 if (rspauth != NULL) {
2766 if (purple_ntlm_verify_signature (signature, rspauth)) {
2767 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
2768 process_input_message(sip, msg);
2769 } else {
2770 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
2771 purple_connection_error(sip->gc, _("Invalid message signature received"));
2772 sip->gc->wants_to_die = TRUE;
2774 } else if (msg->response == 401) {
2775 purple_connection_error(sip->gc, _("Wrong Password"));
2776 sip->gc->wants_to_die = TRUE;
2780 sipmsg_breakdown_free(&msgbd);
2781 } else {
2782 process_input_message(sip, msg);
2787 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
2789 PurpleConnection *gc = data;
2790 struct sipe_account_data *sip = gc->proto_data;
2791 struct sipmsg *msg;
2792 int len;
2793 time_t currtime;
2795 static char buffer[65536];
2796 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
2797 buffer[len] = '\0';
2798 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
2799 msg = sipmsg_parse_msg(buffer);
2800 if (msg) process_input_message(sip, msg);
2804 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
2806 PurpleConnection *gc = data;
2807 struct sipe_account_data *sip = gc->proto_data;
2808 struct sip_connection *conn = NULL;
2809 int readlen, len;
2810 gboolean firstread = TRUE;
2812 /* TODO: It should be possible to make this check unnecessary */
2813 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
2814 purple_ssl_close(gsc);
2815 return;
2818 if (sip->fd != -1)
2819 conn = connection_find(sip, gsc->fd);
2820 if (!conn) {
2821 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
2822 if (sip->fd != -1) purple_ssl_close(gsc);
2823 purple_connection_error(sip->gc, _("Connection not found; Please try to connect again.\n"));
2824 sip->gc->wants_to_die = TRUE;
2825 return;
2828 /* Read all available data from the SSL connection */
2829 do {
2830 /* Increase input buffer size as needed */
2831 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
2832 conn->inbuflen += SIMPLE_BUF_INC;
2833 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
2834 purple_debug_error("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
2837 /* Try to read as much as there is space left in the buffer */
2838 readlen = conn->inbuflen - conn->inbufused - 1;
2839 len = purple_ssl_read(sip->gsc, conn->inbuf + conn->inbufused, readlen);
2841 if (len < 0 && errno == EAGAIN) {
2842 /* Try again later */
2843 return;
2844 } else if (len < 0) {
2845 purple_debug_info("sipe", "sipe_input_cb_ssl: read error\n");
2846 if (sip->gsc){
2847 connection_remove(sip, sip->gsc->fd);
2848 if (sip->fd == gsc->fd) sip->fd = -1;
2849 purple_debug_info("sipe", "sipe_input_cb_ssl: connection_remove\n");
2851 return;
2852 } else if (firstread && (len == 0)) {
2853 purple_connection_error(gc, _("Server has disconnected"));
2854 if (sip->gsc){
2855 connection_remove(sip, sip->gsc->fd);
2856 if (sip->fd == gsc->fd) sip->fd = -1;
2858 return;
2861 conn->inbufused += len;
2862 firstread = FALSE;
2864 /* Equivalence indicates that there is possibly more data to read */
2865 } while (len == readlen);
2867 conn->inbuf[conn->inbufused] = '\0';
2868 process_input(sip, conn);
2872 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
2874 PurpleConnection *gc = data;
2875 struct sipe_account_data *sip = gc->proto_data;
2876 int len;
2877 struct sip_connection *conn = connection_find(sip, source);
2878 if (!conn) {
2879 purple_debug_error("sipe", "Connection not found!\n");
2880 return;
2883 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
2884 conn->inbuflen += SIMPLE_BUF_INC;
2885 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
2888 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
2890 if (len < 0 && errno == EAGAIN)
2891 return;
2892 else if (len <= 0) {
2893 purple_debug_info("sipe", "sipe_input_cb: read error\n");
2894 connection_remove(sip, source);
2895 if (sip->fd == source) sip->fd = -1;
2896 return;
2899 conn->inbufused += len;
2900 conn->inbuf[conn->inbufused] = '\0';
2902 process_input(sip, conn);
2905 /* Callback for new connections on incoming TCP port */
2906 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
2908 PurpleConnection *gc = data;
2909 struct sipe_account_data *sip = gc->proto_data;
2910 struct sip_connection *conn;
2912 int newfd = accept(source, NULL, NULL);
2914 conn = connection_create(sip, newfd);
2916 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
2919 static void login_cb(gpointer data, gint source, const gchar *error_message)
2921 PurpleConnection *gc = data;
2922 struct sipe_account_data *sip;
2923 struct sip_connection *conn;
2925 if (!PURPLE_CONNECTION_IS_VALID(gc))
2927 if (source >= 0)
2928 close(source);
2929 return;
2932 if (source < 0) {
2933 purple_connection_error(gc, _("Could not connect"));
2934 return;
2937 sip = gc->proto_data;
2938 sip->fd = source;
2940 conn = connection_create(sip, source);
2942 sip->registertimeout = purple_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
2944 do_register(sip);
2946 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
2949 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
2951 PurpleConnection *gc = data;
2952 struct sipe_account_data *sip;
2953 struct sip_connection *conn;
2955 if (!PURPLE_CONNECTION_IS_VALID(gc))
2957 if (gsc) purple_ssl_close(gsc);
2958 return;
2961 sip = gc->proto_data;
2962 sip->fd = gsc->fd;
2963 sip->gsc = gsc;
2964 conn = connection_create(sip, sip->fd);
2965 sip->listenport = purple_network_get_port_from_fd(sip->fd);
2966 sip->listenfd = sip->fd;
2967 sip->registertimeout = purple_timeout_add((rand()%100) + 1000, (GSourceFunc)subscribe_timeout, sip);
2969 do_register(sip);
2970 if(sip)
2971 purple_ssl_input_add(sip->gsc, sipe_input_cb_ssl, gc);
2974 static guint sipe_ht_hash_nick(const char *nick)
2976 char *lc = g_utf8_strdown(nick, -1);
2977 guint bucket = g_str_hash(lc);
2978 g_free(lc);
2980 return bucket;
2983 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
2985 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
2988 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
2990 struct sipe_account_data *sip = (struct sipe_account_data*) data;
2992 sip->listen_data = NULL;
2994 if (listenfd == -1) {
2995 purple_connection_error(sip->gc, _("Could not create listen socket"));
2996 return;
2999 sip->fd = listenfd;
3001 sip->listenport = purple_network_get_port_from_fd(sip->fd);
3002 sip->listenfd = sip->fd;
3004 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
3006 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
3007 sip->registertimeout = purple_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
3008 do_register(sip);
3011 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
3013 struct sipe_account_data *sip = (struct sipe_account_data*) data;
3014 int addr_size;
3016 sip->query_data = NULL;
3018 if (!hosts || !hosts->data) {
3019 purple_connection_error(sip->gc, _("Couldn't resolve host"));
3020 return;
3023 addr_size = GPOINTER_TO_INT(hosts->data);
3024 hosts = g_slist_remove(hosts, hosts->data);
3025 memcpy(&(sip->serveraddr), hosts->data, addr_size);
3026 g_free(hosts->data);
3027 hosts = g_slist_remove(hosts, hosts->data);
3028 while (hosts) {
3029 hosts = g_slist_remove(hosts, hosts->data);
3030 g_free(hosts->data);
3031 hosts = g_slist_remove(hosts, hosts->data);
3034 /* create socket for incoming connections */
3035 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
3036 sipe_udp_host_resolved_listen_cb, sip);
3037 if (sip->listen_data == NULL) {
3038 purple_connection_error(sip->gc, _("Could not create listen socket"));
3039 return;
3043 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
3044 gpointer data)
3046 PurpleConnection *gc = data;
3047 struct sipe_account_data *sip;
3049 /* If the connection is already disconnected, we don't need to do anything else */
3050 if (!PURPLE_CONNECTION_IS_VALID(gc))
3051 return;
3053 sip = gc->proto_data;
3054 sip->gsc = NULL;
3056 switch(error) {
3057 case PURPLE_SSL_CONNECT_FAILED:
3058 purple_connection_error(gc, _("Connection Failed"));
3059 break;
3060 case PURPLE_SSL_HANDSHAKE_FAILED:
3061 purple_connection_error(gc, _("SSL Handshake Failed"));
3062 break;
3066 static void
3067 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
3069 struct sipe_account_data *sip = (struct sipe_account_data*) data;
3070 PurpleProxyConnectData *connect_data;
3072 sip->listen_data = NULL;
3074 sip->listenfd = listenfd;
3075 if (sip->listenfd == -1) {
3076 purple_connection_error(sip->gc, _("Could not create listen socket"));
3077 return;
3080 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
3081 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
3082 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
3083 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
3084 sipe_newconn_cb, sip->gc);
3085 purple_debug_info("sipe", "connecting to %s port %d\n",
3086 sip->realhostname, sip->realport);
3087 /* open tcp connection to the server */
3088 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
3089 sip->realport, login_cb, sip->gc);
3091 if (connect_data == NULL) {
3092 purple_connection_error(sip->gc, _("Couldn't create socket"));
3097 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
3099 PurpleAccount *account = sip->account;
3100 PurpleConnection *gc = sip->gc;
3102 if (purple_account_get_bool(account, "useport", FALSE)) {
3103 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
3104 port = purple_account_get_int(account, "port", 0);
3105 } else {
3106 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
3109 sip->realhostname = hostname;
3110 sip->realport = port;
3112 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
3113 hostname, port);
3115 /* TODO: is there a good default grow size? */
3116 if (sip->transport != SIPE_TRANSPORT_UDP)
3117 sip->txbuf = purple_circ_buffer_new(0);
3119 if (sip->transport == SIPE_TRANSPORT_TLS) {
3120 /* SSL case */
3121 if (!purple_ssl_is_supported()) {
3122 gc->wants_to_die = TRUE;
3123 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
3124 return;
3127 purple_debug_info("sipe", "using SSL\n");
3129 /* Redirect: delete old connction first */
3130 if (sip->gsc) {
3131 purple_debug_info("sipe", "redirect-close-old gsc %08x gsc/fd %d sip %08x sip/fd %d\n", sip->gsc, sip->gsc->fd, sip, sip->fd);
3132 purple_ssl_close(sip->gsc);
3135 sip->gsc = purple_ssl_connect(account, hostname, port,
3136 login_cb_ssl, sipe_ssl_connect_failure, gc);
3137 if (sip->gsc == NULL) {
3138 purple_connection_error(gc, _("Could not create SSL context"));
3139 return;
3141 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
3142 /* UDP case */
3143 purple_debug_info("sipe", "using UDP\n");
3145 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
3146 if (sip->query_data == NULL) {
3147 purple_connection_error(gc, _("Could not resolve hostname"));
3149 } else {
3150 /* TCP case */
3151 purple_debug_info("sipe", "using TCP\n");
3152 /* create socket for incoming connections */
3153 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
3154 sipe_tcp_connect_listen_cb, sip);
3155 if (sip->listen_data == NULL) {
3156 purple_connection_error(gc, _("Could not create listen socket"));
3157 return;
3162 /* Service list for autodection */
3163 static const struct sipe_service_data service_autodetect[] = {
3164 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
3165 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
3166 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
3167 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
3168 { NULL, NULL, 0 }
3171 /* Service list for SSL/TLS */
3172 static const struct sipe_service_data service_tls[] = {
3173 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
3174 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
3175 { NULL, NULL, 0 }
3178 /* Service list for TCP */
3179 static const struct sipe_service_data service_tcp[] = {
3180 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
3181 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
3182 { NULL, NULL, 0 }
3185 /* Service list for UDP */
3186 static const struct sipe_service_data service_udp[] = {
3187 { "sip", "udp", SIPE_TRANSPORT_UDP },
3188 { NULL, NULL, 0 }
3191 static void srvresolved(PurpleSrvResponse *, int, gpointer);
3192 static void resolve_next_service(struct sipe_account_data *sip,
3193 const struct sipe_service_data *start)
3195 if (start) {
3196 sip->service_data = start;
3197 } else {
3198 sip->service_data++;
3199 if (sip->service_data->service == NULL) {
3200 /* Try connecting to the SIP hostname directly */
3201 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
3202 // If SSL is supported, default to using it; OCS servers aren't configured
3203 // by default to accept TCP
3204 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
3205 gchar * hostname = g_strdup(sip->sipdomain);
3206 create_connection(sip, hostname, 0);
3207 return;
3211 /* Try to resolve next service */
3212 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
3213 sip->service_data->transport,
3214 sip->sipdomain,
3215 srvresolved, sip);
3218 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
3220 struct sipe_account_data *sip = data;
3222 sip->srv_query_data = NULL;
3224 /* find the host to connect to */
3225 if (results) {
3226 gchar *hostname = g_strdup(resp->hostname);
3227 int port = resp->port;
3228 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
3229 hostname, port);
3230 g_free(resp);
3232 sip->transport = sip->service_data->type;
3234 create_connection(sip, hostname, port);
3235 } else {
3236 resolve_next_service(sip, NULL);
3240 static void sipe_login(PurpleAccount *account)
3242 PurpleConnection *gc;
3243 struct sipe_account_data *sip;
3244 gchar **userserver;
3245 const char *transport;
3247 const char *username = purple_account_get_username(account);
3248 gc = purple_account_get_connection(account);
3250 if (strpbrk(username, " \t\v\r\n") != NULL) {
3251 gc->wants_to_die = TRUE;
3252 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
3253 return;
3256 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
3257 sip->gc = gc;
3258 sip->account = account;
3259 sip->registerexpire = 900;
3261 userserver = g_strsplit(username, "@", 2);
3262 purple_connection_set_display_name(gc, userserver[0]);
3263 sip->username = g_strdup(g_strjoin("@", userserver[0], userserver[1], NULL));
3264 sip->sipdomain = g_strdup(userserver[1]);
3265 sip->password = g_strdup(purple_connection_get_password(gc));
3266 g_strfreev(userserver);
3268 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
3270 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
3272 /* TODO: Set the status correctly. */
3273 sip->status = g_strdup("available");
3275 transport = purple_account_get_string(account, "transport", "auto");
3277 if (purple_account_get_bool(account, "useproxy", FALSE)) {
3278 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
3279 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
3280 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
3281 SIPE_TRANSPORT_UDP;
3282 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
3283 } else if (strcmp(transport, "auto") == 0) {
3284 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
3285 } else if (strcmp(transport, "tls") == 0) {
3286 resolve_next_service(sip, service_tls);
3287 } else if (strcmp(transport, "tcp") == 0) {
3288 resolve_next_service(sip, service_tcp);
3289 } else {
3290 resolve_next_service(sip, service_udp);
3294 static void sipe_connection_cleanup(struct sipe_account_data *sip)
3296 connection_free_all(sip);
3298 if (sip->query_data != NULL)
3299 purple_dnsquery_destroy(sip->query_data);
3300 sip->query_data == NULL;
3302 if (sip->srv_query_data != NULL)
3303 purple_srv_cancel(sip->srv_query_data);
3304 sip->srv_query_data = NULL;
3306 if (sip->listen_data != NULL)
3307 purple_network_listen_cancel(sip->listen_data);
3308 sip->listen_data = NULL;
3310 g_free(sip->registrar.nonce);
3311 sip->registrar.nonce = NULL;
3312 g_free(sip->registrar.opaque);
3313 sip->registrar.opaque = NULL;
3314 g_free(sip->registrar.realm);
3315 sip->registrar.realm = NULL;
3316 g_free(sip->registrar.target);
3317 sip->registrar.target = NULL;
3318 g_free(sip->registrar.digest_session_key);
3319 sip->registrar.digest_session_key = NULL;
3320 g_free(sip->registrar.ntlm_key);
3321 sip->registrar.ntlm_key = NULL;
3322 sip->registrar.type = 0;
3323 sip->registrar.retries = 0;
3325 g_free(sip->proxy.nonce);
3326 sip->proxy.nonce = NULL;
3327 g_free(sip->proxy.opaque);
3328 sip->proxy.opaque = NULL;
3329 g_free(sip->proxy.realm);
3330 sip->proxy.realm = NULL;
3331 g_free(sip->proxy.target);
3332 sip->proxy.target = NULL;
3333 g_free(sip->proxy.digest_session_key);
3334 sip->proxy.digest_session_key = NULL;
3335 g_free(sip->proxy.ntlm_key);
3336 sip->proxy.ntlm_key = NULL;
3337 sip->proxy.type = 0;
3338 sip->proxy.retries = 0;
3340 if (sip->txbuf)
3341 purple_circ_buffer_destroy(sip->txbuf);
3342 sip->txbuf = NULL;
3344 g_free(sip->realhostname);
3345 sip->realhostname = NULL;
3347 if (sip->listenpa)
3348 purple_input_remove(sip->listenpa);
3349 sip->listenpa = 0;
3350 if (sip->tx_handler)
3351 purple_input_remove(sip->tx_handler);
3352 sip->tx_handler = 0;
3353 if (sip->resendtimeout)
3354 purple_timeout_remove(sip->resendtimeout);
3355 sip->resendtimeout = 0;
3356 if (sip->registertimeout)
3357 purple_timeout_remove(sip->registertimeout);
3358 sip->registertimeout = 0;
3361 static void sipe_close(PurpleConnection *gc)
3363 struct sipe_account_data *sip = gc->proto_data;
3365 if (sip) {
3366 /* leave all conversations */
3367 im_session_close_all(sip);
3369 /* unregister */
3370 do_register_exp(sip, 0);
3372 sipe_connection_cleanup(sip);
3373 g_free(sip->sipdomain);
3374 g_free(sip->username);
3375 g_free(sip->password);
3377 g_free(gc->proto_data);
3378 gc->proto_data = NULL;
3381 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
3383 PurpleAccount *acct = purple_connection_get_account(gc);
3384 char *id = g_strdup_printf("sip:%s", g_list_nth_data(row, 0));
3385 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
3386 if (conv == NULL)
3387 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
3388 purple_conversation_present(conv);
3389 g_free(id);
3392 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
3395 purple_blist_request_add_buddy(purple_connection_get_account(gc),
3396 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
3399 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
3401 PurpleNotifySearchResults *results;
3402 PurpleNotifySearchColumn *column;
3403 xmlnode *searchResults;
3404 xmlnode *mrow;
3406 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
3407 if (!searchResults) {
3408 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
3409 return FALSE;
3412 results = purple_notify_searchresults_new();
3414 if (results == NULL) {
3415 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
3416 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
3418 xmlnode_free(searchResults);
3419 return FALSE;
3422 column = purple_notify_searchresults_column_new(_("User Name"));
3423 purple_notify_searchresults_column_add(results, column);
3425 column = purple_notify_searchresults_column_new(_("Name"));
3426 purple_notify_searchresults_column_add(results, column);
3428 column = purple_notify_searchresults_column_new(_("Company"));
3429 purple_notify_searchresults_column_add(results, column);
3431 column = purple_notify_searchresults_column_new(_("Country"));
3432 purple_notify_searchresults_column_add(results, column);
3434 column = purple_notify_searchresults_column_new(_("Email"));
3435 purple_notify_searchresults_column_add(results, column);
3437 int match_count = 0;
3438 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
3439 GList *row = NULL;
3441 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
3442 row = g_list_append(row, g_strdup(uri_parts[1]));
3443 g_strfreev(uri_parts);
3445 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
3446 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
3447 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
3448 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
3450 purple_notify_searchresults_row_add(results, row);
3451 match_count++;
3454 gboolean more = FALSE;
3455 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
3456 char *data = xmlnode_get_data_unescaped(mrow);
3457 more = (g_strcasecmp(data, "true") == 0);
3458 g_free(data);
3461 gchar *secondary = g_strdup_printf(
3462 dngettext(GETTEXT_PACKAGE,
3463 "Found %d contact%s:",
3464 "Found %d contacts%s:", match_count),
3465 match_count, more ? _(" (more matched your query)") : "");
3467 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
3468 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
3469 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
3471 g_free(secondary);
3472 xmlnode_free(searchResults);
3473 return TRUE;
3476 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
3478 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
3479 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
3480 unsigned i = 0;
3482 do {
3483 PurpleRequestField *field = entries->data;
3484 const char *id = purple_request_field_get_id(field);
3485 const char *value = purple_request_field_string_get_value(field);
3487 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value);
3489 if (value != NULL) attrs[i++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW, id, value);
3490 } while ((entries = g_list_next(entries)) != NULL);
3491 attrs[i] = NULL;
3493 if (i > 0) {
3494 gchar *query = g_strjoinv(NULL, attrs);
3495 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
3496 send_soap_request_with_cb(gc->proto_data, body,
3497 (TransCallback) process_search_contact_response, NULL);
3498 g_free(body);
3499 g_free(query);
3502 g_strfreev(attrs);
3505 static void sipe_show_find_contact(PurplePluginAction *action)
3507 PurpleConnection *gc = (PurpleConnection *) action->context;
3508 PurpleRequestFields *fields;
3509 PurpleRequestFieldGroup *group;
3510 PurpleRequestField *field;
3512 fields = purple_request_fields_new();
3513 group = purple_request_field_group_new(NULL);
3514 purple_request_fields_add_group(fields, group);
3516 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
3517 purple_request_field_group_add_field(group, field);
3518 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
3519 purple_request_field_group_add_field(group, field);
3520 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
3521 purple_request_field_group_add_field(group, field);
3522 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
3523 purple_request_field_group_add_field(group, field);
3525 purple_request_fields(gc,
3526 _("Search"),
3527 _("Search for a Contact"),
3528 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
3529 fields,
3530 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
3531 _("_Cancel"), NULL,
3532 purple_connection_get_account(gc), NULL, NULL, gc);
3535 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
3537 PurpleConnection *gc = (PurpleConnection *) context;
3538 struct sipe_account_data *sip = gc->proto_data;
3539 GList *menu = NULL;
3540 PurplePluginAction *act;
3542 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
3543 menu = g_list_prepend(menu, act);
3545 menu = g_list_reverse(menu);
3547 return menu;
3550 /* not needed since privacy is checked for every subscribe */
3551 static void dummy_add_deny(PurpleConnection *gc, const char *name) {
3554 static void dummy_permit_deny(PurpleConnection *gc)
3558 static gboolean sipe_plugin_load(PurplePlugin *plugin)
3560 return TRUE;
3564 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
3566 return TRUE;
3570 static void sipe_plugin_destroy(PurplePlugin *plugin)
3574 static PurplePlugin *my_protocol = NULL;
3576 static PurplePluginProtocolInfo prpl_info =
3579 NULL, /* user_splits */
3580 NULL, /* protocol_options */
3581 NO_BUDDY_ICONS, /* icon_spec */
3582 sipe_list_icon, /* list_icon */
3583 NULL, /* list_emblems */
3584 NULL, /* status_text */ // TODO
3585 NULL, /* tooltip_text */ // add custom info to contact tooltip
3586 sipe_status_types, /* away_states */
3587 NULL, /* blist_node_menu */
3588 NULL, /* chat_info */
3589 NULL, /* chat_info_defaults */
3590 sipe_login, /* login */
3591 sipe_close, /* close */
3592 sipe_im_send, /* send_im */
3593 NULL, /* set_info */ // TODO maybe
3594 sipe_send_typing, /* send_typing */
3595 NULL, /* get_info */ // TODO maybe
3596 sipe_set_status, /* set_status */
3597 NULL, /* set_idle */
3598 NULL, /* change_passwd */
3599 sipe_add_buddy, /* add_buddy */
3600 NULL, /* add_buddies */
3601 sipe_remove_buddy, /* remove_buddy */
3602 NULL, /* remove_buddies */
3603 dummy_add_deny, /* add_permit */
3604 dummy_add_deny, /* add_deny */
3605 dummy_add_deny, /* rem_permit */
3606 dummy_add_deny, /* rem_deny */
3607 dummy_permit_deny, /* set_permit_deny */
3608 NULL, /* join_chat */
3609 NULL, /* reject_chat */
3610 NULL, /* get_chat_name */
3611 NULL, /* chat_invite */
3612 NULL, /* chat_leave */
3613 NULL, /* chat_whisper */
3614 NULL, /* chat_send */
3615 sipe_keep_alive, /* keepalive */
3616 NULL, /* register_user */
3617 NULL, /* get_cb_info */ // deprecated
3618 NULL, /* get_cb_away */ // deprecated
3619 sipe_alias_buddy, /* alias_buddy */
3620 sipe_group_buddy, /* group_buddy */
3621 sipe_rename_group, /* rename_group */
3622 NULL, /* buddy_free */
3623 sipe_convo_closed, /* convo_closed */
3624 purple_normalize_nocase, /* normalize */
3625 NULL, /* set_buddy_icon */
3626 sipe_remove_group, /* remove_group */
3627 NULL, /* get_cb_real_name */ // TODO?
3628 NULL, /* set_chat_topic */
3629 NULL, /* find_blist_chat */
3630 NULL, /* roomlist_get_list */
3631 NULL, /* roomlist_cancel */
3632 NULL, /* roomlist_expand_category */
3633 NULL, /* can_receive_file */
3634 NULL, /* send_file */
3635 NULL, /* new_xfer */
3636 NULL, /* offline_message */
3637 NULL, /* whiteboard_prpl_ops */
3638 sipe_send_raw, /* send_raw */
3642 static PurplePluginInfo info = {
3643 PURPLE_PLUGIN_MAGIC,
3644 PURPLE_MAJOR_VERSION,
3645 PURPLE_MINOR_VERSION,
3646 PURPLE_PLUGIN_PROTOCOL, /**< type */
3647 NULL, /**< ui_requirement */
3648 0, /**< flags */
3649 NULL, /**< dependencies */
3650 PURPLE_PRIORITY_DEFAULT, /**< priority */
3651 "prpl-sipe", /**< id */
3652 "Microsoft LCS/OCS", /**< name */
3653 VERSION, /**< version */
3654 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
3655 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
3656 "Anibal Avelar <avelar@gmail.com>, " /**< author */
3657 "Gabriel Burt <gburt@novell.com>", /**< author */
3658 PURPLE_WEBSITE, /**< homepage */
3659 sipe_plugin_load, /**< load */
3660 sipe_plugin_unload, /**< unload */
3661 sipe_plugin_destroy, /**< destroy */
3662 NULL, /**< ui_info */
3663 &prpl_info, /**< extra_info */
3664 NULL,
3665 sipe_actions,
3666 NULL,
3667 NULL,
3668 NULL,
3669 NULL
3672 static void init_plugin(PurplePlugin *plugin)
3674 PurpleAccountUserSplit *split;
3675 PurpleAccountOption *option;
3676 PurpleKeyValuePair *kvp;
3678 #ifdef ENABLE_NLS
3679 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
3680 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
3681 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
3682 #endif
3684 purple_plugin_register(plugin);
3686 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
3687 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3688 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
3689 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3691 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
3692 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3693 // Translators: noun (networking port)
3694 option = purple_account_option_int_new(_("Port"), "port", 5061);
3695 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3697 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
3698 purple_account_option_add_list_item(option, _("Auto"), "auto");
3699 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
3700 purple_account_option_add_list_item(option, _("TCP"), "tcp");
3701 purple_account_option_add_list_item(option, _("UDP"), "udp");
3702 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3704 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
3705 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
3707 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
3708 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3710 // TODO commented out so won't show in the preferences until we fix krb message signing
3711 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
3712 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3714 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
3715 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
3716 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3719 option = purple_account_option_string_new(_("Auth User"), "authuser", "");
3720 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3721 option = purple_account_option_string_new(_("Auth Domain"), "authdomain", "");
3722 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3723 my_protocol = plugin;
3726 /* I had to redefined the function for it load, but works */
3727 gboolean purple_init_plugin(PurplePlugin *plugin){
3728 plugin->info = &(info);
3729 init_plugin((plugin));
3730 sipe_plugin_load((plugin));
3731 return purple_plugin_register(plugin);