Fix double free and potential memory leak in previous commit
[siplcs.git] / src / sipe.c
blobdc850fc7351fbb3878eb707eb92a3457233432d0
1 /**
2 * @file sipe.c
4 * pidgin-sipe
5 *
6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
11 * ***
12 * Thanks to Google's Summer of Code Program and the helpful mentors
13 * ***
15 * Session-based SIP MESSAGE documentation:
16 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #ifndef _WIN32
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <netinet/in.h>
38 #include <net/if.h>
39 #ifdef ENABLE_NLS
40 # include <libintl.h>
41 # define _(String) ((const char *) gettext (String))
42 #else
43 # define _(String) ((const char *) (String))
44 #endif /* ENABLE_NLS */
45 #else
46 #ifdef _DLL
47 #define _WS2TCPIP_H_
48 #define _WINSOCK2API_
49 #define _LIBC_INTERNAL_
50 #endif /* _DLL */
52 #include "internal.h"
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <string.h>
59 #include <glib.h>
62 #include "accountopt.h"
63 #include "blist.h"
64 #include "conversation.h"
65 #include "dnsquery.h"
66 #include "debug.h"
67 #include "notify.h"
68 #include "privacy.h"
69 #include "prpl.h"
70 #include "plugin.h"
71 #include "util.h"
72 #include "version.h"
73 #include "network.h"
74 #include "xmlnode.h"
75 #include "mime.h"
77 #include "sipe.h"
78 #include "sip-ntlm.h"
79 #ifdef USE_KERBEROS
80 #include "sipkrb5.h"
81 #endif /*USE_KERBEROS*/
83 #include "sipmsg.h"
84 #include "sipe-sign.h"
85 #include "dnssrv.h"
86 #include "request.h"
88 /* Keep in sync with sipe_transport_type! */
89 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
90 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
92 /* Status identifiers (see also: sipe_status_types()) */
93 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
94 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
95 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
96 /* PURPLE_STATUS_UNAVAILABLE: */
97 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
98 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
99 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
100 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
101 /* PURPLE_STATUS_AWAY: */
102 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
103 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
104 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
105 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
106 /* ??? PURPLE_STATUS_MOBILE */
107 /* ??? PURPLE_STATUS_TUNE */
109 /* Action name templates */
110 #define ACTION_NAME_PRESENCE "<presence><%s>"
112 static char *gentag()
114 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
117 static gchar *get_epid(struct sipe_account_data *sip)
119 if (!sip->epid) {
120 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
122 return g_strdup(sip->epid);
125 static char *genbranch()
127 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
128 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
129 rand() & 0xFFFF, rand() & 0xFFFF);
132 static char *gencallid()
134 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
135 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
136 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
137 rand() & 0xFFFF, rand() & 0xFFFF);
140 static gchar *find_tag(const gchar *hdr)
142 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
143 if (!tag) {
144 // In case it's at the end and there's no trailing ;
145 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
147 return tag;
151 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
153 return "sipe";
156 static void sipe_plugin_destroy(PurplePlugin *plugin);
158 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
160 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
161 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
162 gpointer data);
164 static void sipe_close(PurpleConnection *gc);
166 static void send_presence_status(struct sipe_account_data *sip);
168 static void sendout_pkt(PurpleConnection *gc, const char *buf);
170 static void sipe_keep_alive(PurpleConnection *gc)
172 struct sipe_account_data *sip = gc->proto_data;
173 if (sip->transport == SIPE_TRANSPORT_UDP) {
174 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
175 gchar buf[2] = {0, 0};
176 purple_debug_info("sipe", "sending keep alive\n");
177 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
178 } else {
179 time_t now = time(NULL);
180 if ((sip->keepalive_timeout > 0) &&
181 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
182 #if PURPLE_VERSION_CHECK(2,4,0)
183 && ((now - gc->last_received) >= sip->keepalive_timeout)
184 #endif
186 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
187 sendout_pkt(gc, "\r\n\r\n");
188 sip->last_keepalive = now;
193 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
195 struct sip_connection *ret = NULL;
196 GSList *entry = sip->openconns;
197 while (entry) {
198 ret = entry->data;
199 if (ret->fd == fd) return ret;
200 entry = entry->next;
202 return NULL;
205 static void sipe_auth_free(struct sip_auth *auth)
207 g_free(auth->nonce);
208 auth->nonce = NULL;
209 g_free(auth->opaque);
210 auth->opaque = NULL;
211 g_free(auth->realm);
212 auth->realm = NULL;
213 g_free(auth->target);
214 auth->target = NULL;
215 g_free(auth->digest_session_key);
216 auth->digest_session_key = NULL;
217 g_free(auth->ntlm_key);
218 auth->ntlm_key = NULL;
219 auth->type = AUTH_TYPE_UNSET;
220 auth->retries = 0;
221 auth->expires = 0;
224 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
226 struct sip_connection *ret = g_new0(struct sip_connection, 1);
227 ret->fd = fd;
228 sip->openconns = g_slist_append(sip->openconns, ret);
229 return ret;
232 static void connection_remove(struct sipe_account_data *sip, int fd)
234 struct sip_connection *conn = connection_find(sip, fd);
235 if (conn) {
236 sip->openconns = g_slist_remove(sip->openconns, conn);
237 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
238 g_free(conn->inbuf);
239 g_free(conn);
243 static void connection_free_all(struct sipe_account_data *sip)
245 struct sip_connection *ret = NULL;
246 GSList *entry = sip->openconns;
247 while (entry) {
248 ret = entry->data;
249 connection_remove(sip, ret->fd);
250 entry = sip->openconns;
254 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
256 const gchar *method = msg->method;
257 const gchar *target = msg->target;
258 gchar noncecount[9];
259 gchar *response;
260 gchar *ret;
261 gchar *tmp = NULL;
262 const char *authdomain = sip->authdomain;
263 const char *authuser = sip->authuser;
264 //const char *krb5_realm;
265 const char *host;
266 //gchar *krb5_token = NULL;
268 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
269 // and do error checking
271 // KRB realm should always be uppercase
272 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
274 if (sip->realhostname) {
275 host = sip->realhostname;
276 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
277 host = purple_account_get_string(sip->account, "proxy", "");
278 } else {
279 host = sip->sipdomain;
282 /*gboolean new_auth = krb5_auth.gss_context == NULL;
283 if (new_auth) {
284 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
287 if (new_auth || force_reauth) {
288 krb5_token = krb5_auth.base64_token;
291 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
292 krb5_token = krb5_auth.base64_token;*/
294 if (!authdomain) {
295 authdomain = "";
298 if (!authuser || strlen(authuser) < 1) {
299 authuser = sip->username;
302 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
303 sprintf(noncecount, "%08d", auth->nc++);
304 response = purple_cipher_http_digest_calculate_response(
305 "md5", method, target, NULL, NULL,
306 auth->nonce, noncecount, NULL, auth->digest_session_key);
307 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
309 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
310 g_free(response);
311 return ret;
312 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
313 // If we have a signature for the message, include that
314 if (msg->signature) {
315 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
316 return tmp;
319 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
320 const gchar *ntlm_key;
321 gchar *gssapi_data;
322 #if GLIB_CHECK_VERSION(2,8,0)
323 const gchar * hostname = g_get_host_name();
324 #else
325 static char hostname[256];
326 int ret = gethostname(hostname, sizeof(hostname));
327 hostname[sizeof(hostname) - 1] = '\0';
328 if (ret == -1 || hostname[0] == '\0') {
329 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
330 g_strerror(errno);
331 strcpy(hostname, "localhost");
333 #endif
334 /*const gchar * hostname = purple_get_host_name();*/
336 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
337 auth->ntlm_key = (gchar *)ntlm_key;
338 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
339 g_free(gssapi_data);
340 return tmp;
343 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
344 return tmp;
345 } else if (auth->type == AUTH_TYPE_KERBEROS) {
346 /* Kerberos */
347 if (auth->nc == 3) {
348 /*if (new_auth || force_reauth) {
349 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
350 if (auth->opaque) {
351 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);
352 } else {
353 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
355 } else {
356 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
357 gchar * mic = "MICTODO";
358 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
359 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
360 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
361 //auth->opaque ? auth->opaque : "", auth->target);
362 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
363 //g_free(mic);
365 return tmp;
367 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
370 sprintf(noncecount, "%08d", auth->nc++);
371 response = purple_cipher_http_digest_calculate_response(
372 "md5", method, target, NULL, NULL,
373 auth->nonce, noncecount, NULL, auth->digest_session_key);
374 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
376 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);
377 g_free(response);
378 return ret;
381 static char *parse_attribute(const char *attrname, const char *source)
383 const char *tmp, *tmp2;
384 char *retval = NULL;
385 int len = strlen(attrname);
387 if (!strncmp(source, attrname, len)) {
388 tmp = source + len;
389 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
390 if (tmp2)
391 retval = g_strndup(tmp, tmp2 - tmp);
392 else
393 retval = g_strdup(tmp);
396 return retval;
399 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
401 int i = 0;
402 const char *authuser;
403 char *tmp;
404 gchar **parts;
405 //const char *krb5_realm;
406 //const char *host;
408 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
409 // and do error checking
411 // KRB realm should always be uppercase
412 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
414 if (sip->realhostname) {
415 host = sip->realhostname;
416 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
417 host = purple_account_get_string(sip->account, "proxy", "");
418 } else {
419 host = sip->sipdomain;
422 authuser = sip->authuser;
424 if (!authuser || strlen(authuser) < 1) {
425 authuser = sip->username;
428 if (!hdr) {
429 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
430 return;
433 if (!g_strncasecmp(hdr, "NTLM", 4)) {
434 auth->type = AUTH_TYPE_NTLM;
435 parts = g_strsplit(hdr+5, "\", ", 0);
436 i = 0;
437 while (parts[i]) {
438 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
439 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
440 g_free(auth->nonce);
441 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
442 g_free(tmp);
444 if ((tmp = parse_attribute("targetname=\"",
445 parts[i]))) {
446 g_free(auth->target);
447 auth->target = tmp;
449 else if ((tmp = parse_attribute("realm=\"",
450 parts[i]))) {
451 g_free(auth->realm);
452 auth->realm = tmp;
454 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
455 g_free(auth->opaque);
456 auth->opaque = tmp;
458 i++;
460 g_strfreev(parts);
461 auth->nc = 1;
462 if (!strstr(hdr, "gssapi-data")) {
463 auth->nc = 1;
464 } else {
465 auth->nc = 3;
467 return;
470 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
471 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
472 auth->type = AUTH_TYPE_KERBEROS;
473 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
474 parts = g_strsplit(hdr+9, "\", ", 0);
475 i = 0;
476 while (parts[i]) {
477 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
478 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
479 /*if (krb5_auth.gss_context == NULL) {
480 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
482 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
483 g_free(tmp);
485 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
486 g_free(auth->target);
487 auth->target = tmp;
488 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
489 g_free(auth->realm);
490 auth->realm = tmp;
491 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
492 g_free(auth->opaque);
493 auth->opaque = tmp;
495 i++;
497 g_strfreev(parts);
498 auth->nc = 3;
499 return;
502 auth->type = AUTH_TYPE_DIGEST;
503 parts = g_strsplit(hdr, " ", 0);
504 while (parts[i]) {
505 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
506 g_free(auth->nonce);
507 auth->nonce = tmp;
509 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
510 g_free(auth->realm);
511 auth->realm = tmp;
513 i++;
515 g_strfreev(parts);
517 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
518 if (auth->realm) {
519 g_free(auth->digest_session_key);
520 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
521 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
523 auth->nc = 1;
527 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
529 PurpleConnection *gc = data;
530 struct sipe_account_data *sip = gc->proto_data;
531 gsize max_write;
532 gssize written;
534 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
536 if (max_write == 0) {
537 if (sip->tx_handler != 0){
538 purple_input_remove(sip->tx_handler);
539 sip->tx_handler = 0;
541 return;
544 written = write(sip->fd, sip->txbuf->outptr, max_write);
546 if (written < 0 && errno == EAGAIN)
547 written = 0;
548 else if (written <= 0) {
549 /*TODO: do we really want to disconnect on a failure to write?*/
550 purple_connection_error(gc, _("Could not write"));
551 return;
554 purple_circ_buffer_mark_read(sip->txbuf, written);
557 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
559 PurpleConnection *gc = data;
560 struct sipe_account_data *sip = gc->proto_data;
561 gsize max_write;
562 gssize written;
564 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
566 if (max_write == 0) {
567 if (sip->tx_handler != 0) {
568 purple_input_remove(sip->tx_handler);
569 sip->tx_handler = 0;
570 return;
574 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
576 if (written < 0 && errno == EAGAIN)
577 written = 0;
578 else if (written <= 0) {
579 /*TODO: do we really want to disconnect on a failure to write?*/
580 purple_connection_error(gc, _("Could not write"));
581 return;
584 purple_circ_buffer_mark_read(sip->txbuf, written);
587 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
589 static void send_later_cb(gpointer data, gint source, const gchar *error)
591 PurpleConnection *gc = data;
592 struct sipe_account_data *sip;
593 struct sip_connection *conn;
595 if (!PURPLE_CONNECTION_IS_VALID(gc))
597 if (source >= 0)
598 close(source);
599 return;
602 if (source < 0) {
603 purple_connection_error(gc, _("Could not connect"));
604 return;
607 sip = gc->proto_data;
608 sip->fd = source;
609 sip->connecting = FALSE;
610 sip->last_keepalive = time(NULL);
612 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
614 /* If there is more to write now, we need to register a handler */
615 if (sip->txbuf->bufused > 0)
616 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
618 conn = connection_create(sip, source);
619 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
622 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
624 struct sipe_account_data *sip;
625 struct sip_connection *conn;
627 if (!PURPLE_CONNECTION_IS_VALID(gc))
629 if (gsc) purple_ssl_close(gsc);
630 return NULL;
633 sip = gc->proto_data;
634 sip->fd = gsc->fd;
635 sip->gsc = gsc;
636 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
637 sip->connecting = FALSE;
638 sip->last_keepalive = time(NULL);
640 conn = connection_create(sip, gsc->fd);
642 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
644 return sip;
647 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
649 PurpleConnection *gc = data;
650 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
651 if (sip == NULL) return;
653 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
655 /* If there is more to write now */
656 if (sip->txbuf->bufused > 0) {
657 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
662 static void sendlater(PurpleConnection *gc, const char *buf)
664 struct sipe_account_data *sip = gc->proto_data;
666 if (!sip->connecting) {
667 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
668 if (sip->transport == SIPE_TRANSPORT_TLS){
669 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
670 } else {
671 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
672 purple_connection_error(gc, _("Couldn't create socket"));
675 sip->connecting = TRUE;
678 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
679 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
681 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
684 static void sendout_pkt(PurpleConnection *gc, const char *buf)
686 struct sipe_account_data *sip = gc->proto_data;
687 time_t currtime = time(NULL);
688 int writelen = strlen(buf);
690 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
691 if (sip->transport == SIPE_TRANSPORT_UDP) {
692 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
693 purple_debug_info("sipe", "could not send packet\n");
695 } else {
696 int ret;
697 if (sip->fd < 0) {
698 sendlater(gc, buf);
699 return;
702 if (sip->tx_handler) {
703 ret = -1;
704 errno = EAGAIN;
705 } else{
706 if (sip->gsc){
707 ret = purple_ssl_write(sip->gsc, buf, writelen);
708 }else{
709 ret = write(sip->fd, buf, writelen);
713 if (ret < 0 && errno == EAGAIN)
714 ret = 0;
715 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
716 sendlater(gc, buf);
717 return;
720 if (ret < writelen) {
721 if (!sip->tx_handler){
722 if (sip->gsc){
723 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
725 else{
726 sip->tx_handler = purple_input_add(sip->fd,
727 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
728 gc);
732 /* XXX: is it OK to do this? You might get part of a request sent
733 with part of another. */
734 if (sip->txbuf->bufused > 0)
735 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
737 purple_circ_buffer_append(sip->txbuf, buf + ret,
738 writelen - ret);
743 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
745 sendout_pkt(gc, buf);
746 return len;
749 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
751 GSList *tmp = msg->headers;
752 gchar *name;
753 gchar *value;
754 GString *outstr = g_string_new("");
755 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
756 while (tmp) {
757 name = ((struct siphdrelement*) (tmp->data))->name;
758 value = ((struct siphdrelement*) (tmp->data))->value;
759 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
760 tmp = g_slist_next(tmp);
762 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
763 sendout_pkt(sip->gc, outstr->str);
764 g_string_free(outstr, TRUE);
767 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
769 gchar * buf;
770 if (sip->registrar.ntlm_key) {
771 struct sipmsg_breakdown msgbd;
772 gchar *signature_input_str;
773 msgbd.msg = msg;
774 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
775 msgbd.rand = g_strdup_printf("%08x", g_random_int());
776 sip->registrar.ntlm_num++;
777 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
778 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
779 if (signature_input_str != NULL) {
780 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
781 msg->rand = g_strdup(msgbd.rand);
782 msg->num = g_strdup(msgbd.num);
783 g_free(signature_input_str);
785 sipmsg_breakdown_free(&msgbd);
788 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
789 buf = auth_header(sip, &sip->registrar, msg);
790 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
791 sipmsg_add_header(msg, "Authorization", buf);
792 } else {
793 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
795 g_free(buf);
796 } else if (!strcmp(method,"SUBSCRIBE") || !strcmp(method,"SERVICE") || !strcmp(method,"MESSAGE") || !strcmp(method,"INVITE") || !strcmp(method, "ACK") || !strcmp(method, "NOTIFY") || !strcmp(method, "BYE") || !strcmp(method, "INFO") || !strcmp(method, "OPTIONS")) {
797 sip->registrar.nc = 3;
798 sip->registrar.type = AUTH_TYPE_NTLM;
800 buf = auth_header(sip, &sip->registrar, msg);
801 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
802 g_free(buf);
803 } else {
804 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
808 static char *get_contact(struct sipe_account_data *sip)
810 return g_strdup(sip->contact);
814 * unused. Needed?
815 static char *get_contact_service(struct sipe_account_data *sip)
817 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()));
818 //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);
822 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
823 const char *text, const char *body)
825 gchar *name;
826 gchar *value;
827 GString *outstr = g_string_new("");
828 struct sipe_account_data *sip = gc->proto_data;
829 gchar *contact;
830 GSList *tmp;
832 sipmsg_remove_header(msg, "ms-user-data");
834 contact = get_contact(sip);
835 sipmsg_remove_header(msg, "Contact");
836 sipmsg_add_header(msg, "Contact", contact);
837 g_free(contact);
839 /* When sending the acknowlegements and errors, the content length from the original
840 message is still here, but there is no body; we need to make sure we're sending the
841 correct content length */
842 sipmsg_remove_header(msg, "Content-Length");
843 if (body) {
844 gchar len[12];
845 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
846 sipmsg_add_header(msg, "Content-Length", len);
847 } else {
848 sipmsg_remove_header(msg, "Content-Type");
849 sipmsg_add_header(msg, "Content-Length", "0");
852 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
853 //gchar * mic = "MICTODO";
854 msg->response = code;
856 sipmsg_remove_header(msg, "Authentication-Info");
857 sign_outgoing_message(msg, sip, msg->method);
859 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
860 tmp = msg->headers;
861 while (tmp) {
862 name = ((struct siphdrelement*) (tmp->data))->name;
863 value = ((struct siphdrelement*) (tmp->data))->value;
865 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
866 tmp = g_slist_next(tmp);
868 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
869 sendout_pkt(gc, outstr->str);
870 g_string_free(outstr, TRUE);
873 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
875 if (trans->msg) sipmsg_free(trans->msg);
876 sip->transactions = g_slist_remove(sip->transactions, trans);
877 g_free(trans);
880 static struct transaction *
881 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
883 struct transaction *trans = g_new0(struct transaction, 1);
884 trans->time = time(NULL);
885 trans->msg = (struct sipmsg *)msg;
886 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
887 trans->callback = callback;
888 sip->transactions = g_slist_append(sip->transactions, trans);
889 return trans;
892 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
894 struct transaction *trans;
895 GSList *transactions = sip->transactions;
896 gchar *cseq = sipmsg_find_header(msg, "CSeq");
898 while (transactions) {
899 trans = transactions->data;
900 if (!strcmp(trans->cseq, cseq)) {
901 return trans;
903 transactions = transactions->next;
906 return NULL;
909 static struct transaction *
910 send_sip_request(PurpleConnection *gc, const gchar *method,
911 const gchar *url, const gchar *to, const gchar *addheaders,
912 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
914 struct sipe_account_data *sip = gc->proto_data;
915 const char *addh = "";
916 char *buf;
917 struct sipmsg *msg;
918 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
919 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
920 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
921 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
922 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
923 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
924 gchar *route = strdup("");
925 gchar *epid = get_epid(sip); // TODO generate one per account/login
926 struct transaction *trans;
928 if (dialog && dialog->routes)
930 GSList *iter = dialog->routes;
932 while(iter)
934 char *tmp = route;
935 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
936 g_free(tmp);
937 iter = g_slist_next(iter);
941 if (!ourtag && !dialog) {
942 ourtag = gentag();
945 if (!strcmp(method, "REGISTER")) {
946 if (sip->regcallid) {
947 g_free(callid);
948 callid = g_strdup(sip->regcallid);
949 } else {
950 sip->regcallid = g_strdup(callid);
954 if (addheaders) addh = addheaders;
956 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
957 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
958 "From: <sip:%s>%s%s;epid=%s\r\n"
959 "To: <%s>%s%s%s%s\r\n"
960 "Max-Forwards: 70\r\n"
961 "CSeq: %d %s\r\n"
962 "User-Agent: %s\r\n"
963 "Call-ID: %s\r\n"
964 "%s%s"
965 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
966 method,
967 dialog && dialog->request ? dialog->request : url,
968 TRANSPORT_DESCRIPTOR,
969 purple_network_get_my_ip(-1),
970 sip->listenport,
971 branch ? ";branch=" : "",
972 branch ? branch : "",
973 sip->username,
974 ourtag ? ";tag=" : "",
975 ourtag ? ourtag : "",
976 epid,
978 theirtag ? ";tag=" : "",
979 theirtag ? theirtag : "",
980 theirepid ? ";epid=" : "",
981 theirepid ? theirepid : "",
982 dialog ? ++dialog->cseq : ++sip->cseq,
983 method,
984 useragent,
985 callid,
986 route,
987 addh,
988 body ? strlen(body) : 0,
989 body ? body : "");
992 //printf ("parsing msg buf:\n%s\n\n", buf);
993 msg = sipmsg_parse_msg(buf);
995 g_free(buf);
996 g_free(ourtag);
997 g_free(theirtag);
998 g_free(theirepid);
999 g_free(branch);
1000 g_free(callid);
1001 g_free(route);
1002 g_free(epid);
1004 sign_outgoing_message (msg, sip, method);
1006 buf = sipmsg_to_string (msg);
1008 /* add to ongoing transactions */
1009 trans = transactions_add_buf(sip, msg, tc);
1010 sendout_pkt(gc, buf);
1011 g_free(buf);
1013 return trans;
1016 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1018 gchar *from = g_strdup_printf("sip:%s", sip->username);
1019 gchar *contact = get_contact(sip);
1020 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1021 "Content-Type: application/SOAP+xml\r\n",contact);
1023 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1024 tr->payload = payload;
1026 g_free(from);
1027 g_free(contact);
1028 g_free(hdr);
1031 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1033 send_soap_request_with_cb(sip, body, NULL, NULL);
1036 static char *get_contact_register(struct sipe_account_data *sip)
1038 char *epid = get_epid(sip);
1039 char *uuid = generateUUIDfromEPID(epid);
1040 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
1041 g_free(uuid);
1042 g_free(epid);
1043 return(buf);
1046 static void do_register_exp(struct sipe_account_data *sip, int expire)
1048 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1049 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1050 char *to = g_strdup_printf("sip:%s", sip->username);
1051 char *contact = get_contact_register(sip);
1052 char *hdr = g_strdup_printf("Contact: %s\r\n"
1053 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1054 "Event: registration\r\n"
1055 "Allow-Events: presence\r\n"
1056 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1057 "%s", contact, expires);
1058 g_free(contact);
1059 g_free(expires);
1061 sip->registerstatus = 1;
1063 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1064 process_register_response);
1066 g_free(hdr);
1067 g_free(uri);
1068 g_free(to);
1071 static void do_register_cb(struct sipe_account_data *sip, void *unused)
1073 do_register_exp(sip, -1);
1074 sip->reregister_set = FALSE;
1077 static void do_register(struct sipe_account_data *sip)
1079 do_register_exp(sip, -1);
1083 * Returns URI from provided To or From header.
1085 * Needs to g_free() after use.
1087 * @return URI with sip: prefix
1089 static gchar *parse_from(const gchar *hdr)
1091 gchar *from;
1092 const gchar *tmp, *tmp2 = hdr;
1094 if (!hdr) return NULL;
1095 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1096 tmp = strchr(hdr, '<');
1098 /* i hate the different SIP UA behaviours... */
1099 if (tmp) { /* sip address in <...> */
1100 tmp2 = tmp + 1;
1101 tmp = strchr(tmp2, '>');
1102 if (tmp) {
1103 from = g_strndup(tmp2, tmp - tmp2);
1104 } else {
1105 purple_debug_info("sipe", "found < without > in From\n");
1106 return NULL;
1108 } else {
1109 tmp = strchr(tmp2, ';');
1110 if (tmp) {
1111 from = g_strndup(tmp2, tmp - tmp2);
1112 } else {
1113 from = g_strdup(tmp2);
1116 purple_debug_info("sipe", "got %s\n", from);
1117 return from;
1120 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1122 va_list args;
1123 xmlnode * node = NULL;
1124 const gchar * name;
1126 va_start(args, parent);
1127 while ((name = va_arg(args, const char *)) != NULL) {
1128 node = xmlnode_get_child(parent, name);
1129 if (node == NULL) return NULL;
1130 parent = node;
1132 va_end(args);
1134 return node;
1138 static void
1139 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1141 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1142 send_soap_request(sip, body);
1143 g_free(body);
1146 static void
1147 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1149 if (allow) {
1150 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1151 } else {
1152 purple_debug_info("sipe", "Blocking contact %s\n", who);
1155 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1158 static
1159 void sipe_auth_user_cb(void * data)
1161 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1162 if (!job) return;
1164 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1165 g_free(job);
1168 static
1169 void sipe_deny_user_cb(void * data)
1171 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1172 if (!job) return;
1174 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1175 g_free(job);
1178 static void
1179 sipe_add_permit(PurpleConnection *gc, const char *name)
1181 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1182 sipe_contact_allow_deny(sip, name, TRUE);
1185 static void
1186 sipe_add_deny(PurpleConnection *gc, const char *name)
1188 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1189 sipe_contact_allow_deny(sip, name, FALSE);
1192 /*static void
1193 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1195 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1196 sipe_contact_set_acl(sip, name, "");
1199 static void
1200 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1202 xmlnode *watchers;
1203 xmlnode *watcher;
1204 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1205 if (msg->response != 0 && msg->response != 200) return;
1207 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1209 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1210 if (!watchers) return;
1212 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1213 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1214 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1215 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1217 // TODO pull out optional displayName to pass as alias
1218 if (remote_user) {
1219 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1220 job->who = remote_user;
1221 job->sip = sip;
1222 purple_account_request_authorization(
1223 sip->account,
1224 remote_user,
1225 NULL, // id
1226 alias,
1227 NULL, // message
1228 on_list,
1229 sipe_auth_user_cb,
1230 sipe_deny_user_cb,
1231 (void *) job);
1236 xmlnode_free(watchers);
1237 return;
1240 static void
1241 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1243 PurpleGroup * purple_group = purple_find_group(group->name);
1244 if (!purple_group) {
1245 purple_group = purple_group_new(group->name);
1246 purple_blist_add_group(purple_group, NULL);
1249 if (purple_group) {
1250 group->purple_group = purple_group;
1251 sip->groups = g_slist_append(sip->groups, group);
1252 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1253 } else {
1254 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1258 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1260 struct sipe_group *group;
1261 GSList *entry;
1262 if (sip == NULL) {
1263 return NULL;
1266 entry = sip->groups;
1267 while (entry) {
1268 group = entry->data;
1269 if (group->id == id) {
1270 return group;
1272 entry = entry->next;
1274 return NULL;
1277 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1279 struct sipe_group *group;
1280 GSList *entry;
1281 if (sip == NULL) {
1282 return NULL;
1285 entry = sip->groups;
1286 while (entry) {
1287 group = entry->data;
1288 if (!strcmp(group->name, name)) {
1289 return group;
1291 entry = entry->next;
1293 return NULL;
1296 static void
1297 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1299 gchar *body;
1300 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1301 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1302 send_soap_request(sip, body);
1303 g_free(body);
1304 g_free(group->name);
1305 group->name = g_strdup(name);
1309 * Only appends if no such value already stored.
1310 * Like Set in Java.
1312 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1313 GSList * res = list;
1314 if (!g_slist_find_custom(list, data, func)) {
1315 res = g_slist_insert_sorted(list, data, func);
1317 return res;
1320 static int
1321 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1322 return group1->id - group2->id;
1326 * Returns string like "2 4 7 8" - group ids buddy belong to.
1328 static gchar *
1329 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1330 int i = 0;
1331 gchar *res;
1332 //creating array from GList, converting int to gchar*
1333 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1334 GSList *entry = buddy->groups;
1335 while (entry) {
1336 struct sipe_group * group = entry->data;
1337 ids_arr[i] = g_strdup_printf("%d", group->id);
1338 entry = entry->next;
1339 i++;
1341 ids_arr[i] = NULL;
1342 res = g_strjoinv(" ", ids_arr);
1343 g_strfreev(ids_arr);
1344 return res;
1348 * Sends buddy update to server
1350 static void
1351 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1353 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1354 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1356 if (buddy && purple_buddy) {
1357 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1358 gchar *body;
1359 gchar *groups = sipe_get_buddy_groups_string(buddy);
1360 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1362 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1363 alias, groups, "true", buddy->name, sip->contacts_delta++
1365 send_soap_request(sip, body);
1366 g_free(groups);
1367 g_free(body);
1371 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1373 if (msg->response == 200) {
1374 struct sipe_group *group;
1375 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1376 xmlnode *xml;
1377 xmlnode *node;
1378 char *group_id;
1379 struct sipe_buddy *buddy;
1381 xml = xmlnode_from_str(msg->body, msg->bodylen);
1382 if (!xml) {
1383 g_free(ctx);
1384 return FALSE;
1387 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1388 if (!node) {
1389 g_free(ctx);
1390 xmlnode_free(xml);
1391 return FALSE;
1394 group_id = xmlnode_get_data(node);
1395 if (!group_id) {
1396 g_free(ctx);
1397 xmlnode_free(xml);
1398 return FALSE;
1401 group = g_new0(struct sipe_group, 1);
1402 group->id = (int)g_ascii_strtod(group_id, NULL);
1403 g_free(group_id);
1404 group->name = ctx->group_name;
1406 sipe_group_add(sip, group);
1408 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1409 if (buddy) {
1410 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1413 sipe_group_set_user(sip, ctx->user_name);
1415 g_free(ctx);
1416 xmlnode_free(xml);
1417 return TRUE;
1419 return FALSE;
1422 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1424 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1425 gchar *body;
1426 ctx->group_name = g_strdup(name);
1427 ctx->user_name = g_strdup(who);
1429 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1430 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1431 g_free(body);
1435 * Data structure for scheduled actions
1437 typedef void (*Action) (struct sipe_account_data *, void *);
1439 struct scheduled_action {
1440 /**
1441 * Name of action.
1442 * Format is <Event>[<Data>...]
1443 * Example: <presence><sip:user@domain.com> or <registration>
1445 gchar *name;
1446 guint timeout_handler;
1447 gboolean repetitive;
1448 Action action;
1449 struct sipe_account_data *sip;
1450 void *payload;
1454 * A timer callback
1455 * Should return FALSE if repetitive action is not needed
1457 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1459 gboolean ret;
1460 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1461 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1462 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1463 (sched_action->action)(sched_action->sip, sched_action->payload);
1464 ret = sched_action->repetitive;
1465 g_free(sched_action->payload);
1466 g_free(sched_action->name);
1467 g_free(sched_action);
1468 return ret;
1472 * Kills action timer effectively cancelling
1473 * scheduled action
1475 * @param name of action
1477 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1479 GSList *entry;
1481 if (!sip->timeouts || !name) return;
1483 entry = sip->timeouts;
1484 while (entry) {
1485 struct scheduled_action *sched_action = entry->data;
1486 if(!strcmp(sched_action->name, name)) {
1487 GSList *to_delete = entry;
1488 entry = entry->next;
1489 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1490 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1491 purple_timeout_remove(sched_action->timeout_handler);
1492 g_free(sched_action->payload);
1493 g_free(sched_action->name);
1494 g_free(sched_action);
1495 } else {
1496 entry = entry->next;
1502 * Do schedule action for execution in the future.
1503 * Non repetitive execution.
1505 * @param name of action (will be copied)
1506 * @param timeout in seconds
1507 * @action callback function
1508 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1510 static void sipe_schedule_action(const gchar *name, int timeout, Action action, struct sipe_account_data *sip, void *payload)
1512 struct scheduled_action *sched_action;
1514 /* Make sure each action only exists once */
1515 sipe_cancel_scheduled_action(sip, name);
1517 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1518 sched_action = g_new0(struct scheduled_action, 1);
1519 sched_action->repetitive = FALSE;
1520 sched_action->name = g_strdup(name);
1521 sched_action->action = action;
1522 sched_action->sip = sip;
1523 sched_action->payload = payload;
1524 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1525 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1526 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1529 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1531 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1533 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1535 process_incoming_notify(sip, msg, FALSE, FALSE);
1537 return TRUE;
1540 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1542 gchar *tmp = *resources_uri;
1543 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1544 g_free(tmp);
1547 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1549 gchar *tmp = *resources_uri;
1550 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1551 if(sbuddy){
1552 if(!sbuddy->resubscribed){ //Only not resubscribed contacts; the first time everybody are included
1553 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1556 g_free(tmp);
1560 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1561 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1562 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1563 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1564 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1567 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip, void *unused)
1569 gchar *to = g_strdup_printf("sip:%s", sip->username);
1570 gchar *contact = get_contact(sip);
1571 gchar *request;
1572 gchar *content;
1573 gchar *resources_uri = g_strdup("");
1574 gchar *require = "";
1575 gchar *accept = "";
1576 gchar *autoextend = "";
1577 gchar *content_type;
1579 if (sip->msrtc_event_categories) {
1581 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1583 require = ", categoryList";
1584 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1585 content_type = "application/msrtc-adrl-categorylist+xml";
1586 content = g_strdup_printf(
1587 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1588 "<action name=\"subscribe\" id=\"63792024\">\n"
1589 "<adhocList>\n%s</adhocList>\n"
1590 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1591 "<category name=\"note\"/>\n"
1592 "<category name=\"state\"/>\n"
1593 "</categoryList>\n"
1594 "</action>\n"
1595 "</batchSub>", sip->username, resources_uri);
1596 } else {
1597 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1598 autoextend = "Supported: com.microsoft.autoextend\r\n";
1599 content_type = "application/adrl+xml";
1600 content = g_strdup_printf(
1601 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1602 "<create xmlns=\"\">\n%s</create>\n"
1603 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1605 g_free(resources_uri);
1607 request = g_strdup_printf(
1608 "Require: adhoclist%s\r\n"
1609 "Supported: eventlist\r\n"
1610 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1611 "Supported: ms-piggyback-first-notify\r\n"
1612 "%sSupported: ms-benotify\r\n"
1613 "Proxy-Require: ms-benotify\r\n"
1614 "Event: presence\r\n"
1615 "Content-Type: %s\r\n"
1616 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1617 g_free(contact);
1619 /* subscribe to buddy presence */
1620 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1621 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1623 g_free(content);
1624 g_free(to);
1625 g_free(request);
1629 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1630 * The user sends a single SUBSCRIBE request to the subscribed contact.
1631 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1635 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1637 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1638 gchar *tmp = get_contact(sip);
1639 gchar *request;
1640 gchar *content;
1641 gchar *autoextend = "";
1643 if (!sip->msrtc_event_categories)
1644 autoextend = "Supported: com.microsoft.autoextend\r\n";
1646 request = g_strdup_printf(
1647 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1648 "Supported: ms-piggyback-first-notify\r\n"
1649 "%sSupported: ms-benotify\r\n"
1650 "Proxy-Require: ms-benotify\r\n"
1651 "Event: presence\r\n"
1652 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1653 "Contact: %s\r\n", autoextend,tmp);
1655 content = g_strdup_printf(
1656 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1657 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1658 "<resource uri=\"%s\"/>\n"
1659 "</adhocList>\n"
1660 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1661 "<category name=\"note\"/>\n"
1662 "<category name=\"state\"/>\n"
1663 "</categoryList>\n"
1664 "</action>\n"
1665 "</batchSub>", sip->username, to
1668 g_free(tmp);
1670 /* subscribe to buddy presence */
1671 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1673 g_free(content);
1674 g_free(to);
1675 g_free(request);
1678 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1680 if (!purple_status_is_active(status))
1681 return;
1683 if (account->gc) {
1684 struct sipe_account_data *sip = account->gc->proto_data;
1686 if (sip) {
1687 g_free(sip->status);
1688 sip->status = g_strdup(purple_status_get_id(status));
1689 send_presence_status(sip);
1694 static void
1695 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1697 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1698 sipe_group_set_user(sip, name);
1701 static void
1702 sipe_group_buddy(PurpleConnection *gc,
1703 const char *who,
1704 const char *old_group_name,
1705 const char *new_group_name)
1707 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1708 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1709 struct sipe_group * old_group = NULL;
1710 struct sipe_group * new_group;
1712 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1713 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1715 if(!buddy) { // buddy not in roaming list
1716 return;
1719 if (old_group_name) {
1720 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1722 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1724 if (old_group) {
1725 buddy->groups = g_slist_remove(buddy->groups, old_group);
1726 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1729 if (!new_group) {
1730 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1731 } else {
1732 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1733 sipe_group_set_user(sip, who);
1737 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1739 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1740 struct sipe_buddy *b;
1742 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1744 // Prepend sip: if needed
1745 if (strncmp("sip:", buddy->name, 4)) {
1746 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1747 purple_blist_rename_buddy(buddy, buf);
1748 g_free(buf);
1751 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1752 b = g_new0(struct sipe_buddy, 1);
1753 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1754 b->name = g_strdup(buddy->name);
1755 g_hash_table_insert(sip->buddies, b->name, b);
1756 sipe_group_buddy(gc, b->name, NULL, group->name);
1757 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1758 } else {
1759 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1763 static void sipe_free_buddy(struct sipe_buddy *buddy)
1765 g_free(buddy->name);
1766 g_free(buddy->annotation);
1767 g_free(buddy->device_name);
1768 g_slist_free(buddy->groups);
1769 g_free(buddy);
1773 * Unassociates buddy from group first.
1774 * Then see if no groups left, removes buddy completely.
1775 * Otherwise updates buddy groups on server.
1777 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1779 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1780 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1781 struct sipe_group *g = NULL;
1783 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1785 if (!b) return;
1787 if (group) {
1788 g = sipe_group_find_by_name(sip, group->name);
1791 if (g) {
1792 b->groups = g_slist_remove(b->groups, g);
1793 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1796 if (g_slist_length(b->groups) < 1) {
1797 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1798 sipe_cancel_scheduled_action(sip, action_name);
1799 g_free(action_name);
1801 g_hash_table_remove(sip->buddies, buddy->name);
1803 if (b->name) {
1804 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1805 send_soap_request(sip, body);
1806 g_free(body);
1809 sipe_free_buddy(b);
1810 } else {
1811 //updates groups on server
1812 sipe_group_set_user(sip, b->name);
1817 static void
1818 sipe_rename_group(PurpleConnection *gc,
1819 const char *old_name,
1820 PurpleGroup *group,
1821 GList *moved_buddies)
1823 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1824 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1825 if (group) {
1826 sipe_group_rename(sip, s_group, group->name);
1827 } else {
1828 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1832 static void
1833 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1835 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1836 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1837 if (s_group) {
1838 gchar *body;
1839 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1840 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1841 send_soap_request(sip, body);
1842 g_free(body);
1844 sip->groups = g_slist_remove(sip->groups, s_group);
1845 g_free(s_group->name);
1846 g_free(s_group);
1847 } else {
1848 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1852 static GList *sipe_status_types(PurpleAccount *acc)
1854 PurpleStatusType *type;
1855 GList *types = NULL;
1857 // Online
1858 type = purple_status_type_new_with_attrs(
1859 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1860 // Translators: noun
1861 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1862 NULL);
1863 types = g_list_append(types, type);
1865 // Busy
1866 type = purple_status_type_new_with_attrs(
1867 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1868 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1869 NULL);
1870 types = g_list_append(types, type);
1872 // Do Not Disturb (not user settable)
1873 type = purple_status_type_new_with_attrs(
1874 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1875 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1876 NULL);
1877 types = g_list_append(types, type);
1879 // Be Right Back
1880 type = purple_status_type_new_with_attrs(
1881 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1882 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1883 NULL);
1884 types = g_list_append(types, type);
1886 // Away
1887 type = purple_status_type_new_with_attrs(
1888 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1889 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1890 NULL);
1891 types = g_list_append(types, type);
1893 //On The Phone
1894 type = purple_status_type_new_with_attrs(
1895 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1896 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1897 NULL);
1898 types = g_list_append(types, type);
1900 //Out To Lunch
1901 type = purple_status_type_new_with_attrs(
1902 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1903 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1904 NULL);
1905 types = g_list_append(types, type);
1907 //Appear Offline
1908 type = purple_status_type_new_full(
1909 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1910 types = g_list_append(types, type);
1912 // Offline
1913 type = purple_status_type_new_full(
1914 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1915 types = g_list_append(types, type);
1917 return types;
1921 * A callback for g_hash_table_foreach
1923 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1925 sipe_subscribe_presence_single(sip, buddy->name);
1929 * Removes entries from purple buddy list
1930 * that does not correspond ones in the roaming contact list.
1932 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1933 GSList *buddies = purple_find_buddies(sip->account, NULL);
1934 GSList *entry = buddies;
1935 struct sipe_buddy *buddy;
1936 PurpleBuddy *b;
1937 PurpleGroup *g;
1939 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1940 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1941 while (entry) {
1942 b = entry->data;
1943 g = purple_buddy_get_group(b);
1944 buddy = g_hash_table_lookup(sip->buddies, b->name);
1945 if(buddy) {
1946 gboolean in_sipe_groups = FALSE;
1947 GSList *entry2 = buddy->groups;
1948 while (entry2) {
1949 struct sipe_group *group = entry2->data;
1950 if (!strcmp(group->name, g->name)) {
1951 in_sipe_groups = TRUE;
1952 break;
1954 entry2 = entry2->next;
1956 if(!in_sipe_groups) {
1957 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1958 purple_blist_remove_buddy(b);
1960 } else {
1961 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1962 purple_blist_remove_buddy(b);
1964 entry = entry->next;
1968 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1970 int len = msg->bodylen;
1972 gchar *tmp = sipmsg_find_header(msg, "Event");
1973 xmlnode *item;
1974 xmlnode *isc;
1975 const gchar *contacts_delta;
1976 xmlnode *group_node;
1977 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1978 return FALSE;
1981 /* Convert the contact from XML to Purple Buddies */
1982 isc = xmlnode_from_str(msg->body, len);
1983 if (!isc) {
1984 return FALSE;
1987 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1988 if (contacts_delta) {
1989 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1992 /* Parse groups */
1993 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1994 struct sipe_group * group = g_new0(struct sipe_group, 1);
1995 const char *name = xmlnode_get_attrib(group_node, "name");
1997 if (!strncmp(name, "~", 1)) {
1998 // TODO translate
1999 name = "Other Contacts";
2001 group->name = g_strdup(name);
2002 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
2004 sipe_group_add(sip, group);
2007 // Make sure we have at least one group
2008 if (g_slist_length(sip->groups) == 0) {
2009 struct sipe_group * group = g_new0(struct sipe_group, 1);
2010 PurpleGroup *purple_group;
2011 // TODO translate
2012 group->name = g_strdup("Other Contacts");
2013 group->id = 1;
2014 purple_group = purple_group_new(group->name);
2015 purple_blist_add_group(purple_group, NULL);
2016 sip->groups = g_slist_append(sip->groups, group);
2019 /* Parse contacts */
2020 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2021 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
2022 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
2023 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
2024 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
2025 gchar **item_groups;
2026 struct sipe_group *group = NULL;
2027 struct sipe_buddy *buddy = NULL;
2028 int i = 0;
2030 // assign to group Other Contacts if nothing else received
2031 if(!groups || !strcmp("", groups) ) {
2032 group = sipe_group_find_by_name(sip, "Other Contacts");
2033 groups = group ? g_strdup_printf("%d", group->id) : "1";
2036 item_groups = g_strsplit(groups, " ", 0);
2038 while (item_groups[i]) {
2039 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2041 // If couldn't find the right group for this contact, just put them in the first group we have
2042 if (group == NULL && g_slist_length(sip->groups) > 0) {
2043 group = sip->groups->data;
2046 if (group != NULL) {
2047 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2048 if (!b){
2049 b = purple_buddy_new(sip->account, buddy_name, uri);
2050 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2053 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2054 if (name != NULL && strlen(name) != 0) {
2055 purple_blist_alias_buddy(b, name);
2059 if (!buddy) {
2060 buddy = g_new0(struct sipe_buddy, 1);
2061 buddy->name = g_strdup(b->name);
2062 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2065 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2067 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2068 } else {
2069 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2070 name);
2073 i++;
2074 } // while, contact groups
2075 g_strfreev(item_groups);
2076 g_free(groups);
2077 g_free(name);
2078 g_free(buddy_name);
2079 g_free(uri);
2081 } // for, contacts
2083 xmlnode_free(isc);
2085 sipe_cleanup_local_blist(sip);
2087 //subscribe to buddies
2088 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2089 if(sip->batched_support){
2090 sipe_subscribe_presence_batched(sip, NULL);
2092 else{
2093 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2095 sip->subscribed_buddies = TRUE;
2098 return 0;
2102 * Subscribe roaming contacts
2104 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2106 gchar *to = g_strdup_printf("sip:%s", sip->username);
2107 gchar *tmp = get_contact(sip);
2108 gchar *hdr = g_strdup_printf(
2109 "Event: vnd-microsoft-roaming-contacts\r\n"
2110 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2111 "Supported: com.microsoft.autoextend\r\n"
2112 "Supported: ms-benotify\r\n"
2113 "Proxy-Require: ms-benotify\r\n"
2114 "Supported: ms-piggyback-first-notify\r\n"
2115 "Contact: %s\r\n", tmp);
2116 g_free(tmp);
2118 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2119 g_free(to);
2120 g_free(hdr);
2123 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, void *unused)
2125 gchar *to = g_strdup_printf("sip:%s", sip->username);
2126 gchar *tmp = get_contact(sip);
2127 gchar *hdr = g_strdup_printf(
2128 "Event: presence.wpending\r\n"
2129 "Accept: text/xml+msrtc.wpending\r\n"
2130 "Supported: com.microsoft.autoextend\r\n"
2131 "Supported: ms-benotify\r\n"
2132 "Proxy-Require: ms-benotify\r\n"
2133 "Supported: ms-piggyback-first-notify\r\n"
2134 "Contact: %s\r\n", tmp);
2135 g_free(tmp);
2137 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2138 g_free(to);
2139 g_free(hdr);
2143 * Fires on deregistration event initiated by server.
2144 * [MS-SIPREGE] SIP extension.
2147 // 2007 Example
2149 // Content-Type: text/registration-event
2150 // subscription-state: terminated;expires=0
2151 // ms-diagnostics-public: 4141;reason="User disabled"
2153 // deregistered;event=rejected
2155 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2157 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2158 gchar *event = NULL;
2159 gchar *reason = NULL;
2160 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2162 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2163 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2165 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2166 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2167 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2168 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2169 } else {
2170 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2171 return;
2174 if (warning != NULL) {
2175 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2176 } else { // for LCS2005
2177 int error_id = 0;
2178 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2179 error_id = 4140; // [MS-SIPREGE]
2180 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2181 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2182 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2183 error_id = 4141;
2184 reason = g_strdup(_("User disabled")); // [MS-OCER]
2185 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2186 error_id = 4142;
2187 reason = g_strdup(_("User moved")); // [MS-OCER]
2190 g_free(event);
2191 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2192 g_free(reason);
2194 sip->gc->wants_to_die = TRUE;
2195 purple_connection_error(sip->gc, warning);
2196 g_free(warning);
2200 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2202 const gchar *contacts_delta;
2203 xmlnode *xml;
2205 xml = xmlnode_from_str(msg->body, msg->bodylen);
2206 if (!xml)
2208 return;
2211 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2212 if (contacts_delta)
2214 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2217 xmlnode_free(xml);
2223 * When we receive some self (BE) NOTIFY with a new subscriber
2224 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2228 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2230 gchar *contact;
2231 gchar *to;
2232 xmlnode *xml;
2233 xmlnode *node;
2234 char *display_name = NULL;
2235 PurpleBuddy *pbuddy;
2236 const char *alias;
2237 char *uri_alias;
2238 char *uri_user;
2240 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2242 xml = xmlnode_from_str(msg->body, msg->bodylen);
2243 if (!xml) return;
2245 contact = get_contact(sip);
2246 to = g_strdup_printf("sip:%s", sip->username);
2248 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2249 const char *user;
2250 const char *acknowledged;
2251 gchar *hdr;
2252 gchar *body;
2254 user = xmlnode_get_attrib(node, "user");
2255 if (!user) continue;
2256 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2257 uri_user = g_strdup_printf("sip:%s", user);
2258 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2259 if(pbuddy){
2260 alias = purple_buddy_get_local_alias(pbuddy);
2261 uri_alias = g_strdup_printf("sip:%s", alias);
2262 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2263 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2264 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2265 purple_blist_alias_buddy(pbuddy, display_name);
2267 g_free(uri_alias);
2269 g_free(uri_user);
2271 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2272 if(!g_ascii_strcasecmp(acknowledged,"false")){
2273 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2274 hdr = g_strdup_printf(
2275 "Contact: %s\r\n"
2276 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2278 body = g_strdup_printf(
2279 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2280 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2281 "</setSubscribers>", user);
2283 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2284 g_free(body);
2285 g_free(hdr);
2289 g_free(to);
2290 g_free(contact);
2291 xmlnode_free(xml);
2294 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2296 gchar *to = g_strdup_printf("sip:%s", sip->username);
2297 gchar *tmp = get_contact(sip);
2298 gchar *hdr = g_strdup_printf(
2299 "Event: vnd-microsoft-roaming-ACL\r\n"
2300 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2301 "Supported: com.microsoft.autoextend\r\n"
2302 "Supported: ms-benotify\r\n"
2303 "Proxy-Require: ms-benotify\r\n"
2304 "Supported: ms-piggyback-first-notify\r\n"
2305 "Contact: %s\r\n", tmp);
2306 g_free(tmp);
2308 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2309 g_free(to);
2310 g_free(hdr);
2314 * To request for presence information about the user, access level settings that have already been configured by the user
2315 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2316 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2319 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2321 gchar *to = g_strdup_printf("sip:%s", sip->username);
2322 gchar *tmp = get_contact(sip);
2323 gchar *hdr = g_strdup_printf(
2324 "Event: vnd-microsoft-roaming-self\r\n"
2325 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2326 "Supported: ms-benotify\r\n"
2327 "Proxy-Require: ms-benotify\r\n"
2328 "Supported: ms-piggyback-first-notify\r\n"
2329 "Contact: %s\r\n"
2330 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2332 gchar *body=g_strdup(
2333 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2334 "<roaming type=\"categories\"/>"
2335 "<roaming type=\"containers\"/>"
2336 "<roaming type=\"subscribers\"/></roamingList>");
2338 g_free(tmp);
2339 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2340 g_free(body);
2341 g_free(to);
2342 g_free(hdr);
2346 * For 2005 version
2348 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2350 gchar *to = g_strdup_printf("sip:%s", sip->username);
2351 gchar *tmp = get_contact(sip);
2352 gchar *hdr = g_strdup_printf(
2353 "Event: vnd-microsoft-provisioning\r\n"
2354 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2355 "Supported: com.microsoft.autoextend\r\n"
2356 "Supported: ms-benotify\r\n"
2357 "Proxy-Require: ms-benotify\r\n"
2358 "Supported: ms-piggyback-first-notify\r\n"
2359 "Expires: 0\r\n"
2360 "Contact: %s\r\n", tmp);
2362 g_free(tmp);
2363 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2364 g_free(to);
2365 g_free(hdr);
2368 /** Subscription for provisioning information to help with initial
2369 * configuration. This subscription is a one-time query (denoted by the Expires header,
2370 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2371 * configuration, meeting policies, and policy settings that Communicator must enforce.
2372 * TODO: for what we need this information.
2375 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2377 gchar *to = g_strdup_printf("sip:%s", sip->username);
2378 gchar *tmp = get_contact(sip);
2379 gchar *hdr = g_strdup_printf(
2380 "Event: vnd-microsoft-provisioning-v2\r\n"
2381 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2382 "Supported: com.microsoft.autoextend\r\n"
2383 "Supported: ms-benotify\r\n"
2384 "Proxy-Require: ms-benotify\r\n"
2385 "Supported: ms-piggyback-first-notify\r\n"
2386 "Expires: 0\r\n"
2387 "Contact: %s\r\n"
2388 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2389 gchar *body = g_strdup(
2390 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2391 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2392 "<provisioningGroup name=\"ucPolicy\"/>"
2393 "</provisioningGroupList>");
2395 g_free(tmp);
2396 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2397 g_free(body);
2398 g_free(to);
2399 g_free(hdr);
2402 /* IM Session (INVITE and MESSAGE methods) */
2404 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2406 struct sip_im_session *session;
2407 GSList *entry;
2408 if (sip == NULL || who == NULL) {
2409 return NULL;
2412 entry = sip->im_sessions;
2413 while (entry) {
2414 session = entry->data;
2415 if ((who != NULL && !strcmp(who, session->with))) {
2416 return session;
2418 entry = entry->next;
2420 return NULL;
2423 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2425 struct sip_im_session *session = find_im_session(sip, who);
2426 if (!session) {
2427 session = g_new0(struct sip_im_session, 1);
2428 session->with = g_strdup(who);
2429 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2430 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2432 return session;
2435 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2437 struct sip_dialog *dialog = session->dialog;
2438 GSList *entry;
2440 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2442 if (dialog) {
2443 entry = dialog->routes;
2444 while (entry) {
2445 g_free(entry->data);
2446 entry = g_slist_remove(entry, entry->data);
2448 entry = dialog->supported;
2449 while (entry) {
2450 g_free(entry->data);
2451 entry = g_slist_remove(entry, entry->data);
2453 g_free(dialog->callid);
2454 g_free(dialog->ourtag);
2455 g_free(dialog->theirtag);
2456 g_free(dialog->theirepid);
2457 g_free(dialog->request);
2459 g_free(session->dialog);
2461 entry = session->outgoing_message_queue;
2462 while (entry) {
2463 g_free(entry->data);
2464 entry = g_slist_remove(entry, entry->data);
2467 g_hash_table_destroy(session->unconfirmed_messages);
2469 g_free(session->with);
2470 g_free(session);
2473 static gboolean
2474 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2476 gboolean ret = TRUE;
2478 if (msg->response != 200) {
2479 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2480 return FALSE;
2483 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2485 return ret;
2489 * Asks UA/proxy about its capabilities.
2491 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2493 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2494 gchar *contact = get_contact(sip);
2495 gchar *request;
2496 request = g_strdup_printf(
2497 "Accept: application/sdp\r\n"
2498 "Contact: %s\r\n", contact);
2500 g_free(contact);
2502 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2504 g_free(to);
2505 g_free(request);
2508 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2510 char *msg, *msg_tmp;
2511 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2512 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2513 g_free(msg_tmp);
2514 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2515 "possibly because one or more persons are offline:\n%s") ,
2516 msg ? msg : "");
2517 purple_conv_present_error(with, sip->account, msg_tmp);
2518 g_free(msg);
2519 g_free(msg_tmp);
2522 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2524 static gboolean
2525 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2527 gboolean ret = TRUE;
2528 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2529 struct sip_im_session * session = find_im_session(sip, with);
2530 struct sip_dialog *dialog;
2531 gchar *cseq;
2532 char *key;
2533 gchar *message;
2535 if (!session) {
2536 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2537 g_free(with);
2538 return FALSE;
2541 dialog = session->dialog;
2542 if (!dialog) {
2543 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2544 g_free(with);
2545 return FALSE;
2548 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2549 key = g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq));
2550 g_free(cseq);
2551 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2553 if (msg->response != 200) {
2554 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2556 sipe_present_message_undelivered_err(with, sip, message);
2557 im_session_destroy(sip, session);
2558 ret = FALSE;
2559 } else {
2560 g_hash_table_remove(session->unconfirmed_messages, key);
2561 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2562 key, g_hash_table_size(session->unconfirmed_messages));
2565 g_free(key);
2566 g_free(with);
2568 sipe_im_process_queue(sip, session);
2569 return ret;
2572 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2574 gchar *hdr;
2575 gchar *fullto;
2576 gchar *tmp;
2577 char *msgformat;
2578 char *msgtext;
2579 gchar *msgr_value;
2580 gchar *msgr;
2582 if (strncmp("sip:", session->with, 4)) {
2583 fullto = g_strdup_printf("sip:%s", session->with);
2584 } else {
2585 fullto = g_strdup(session->with);
2588 sipe_parse_html(msg, &msgformat, &msgtext);
2589 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2591 msgr_value = sipmsg_get_msgr_string(msgformat);
2592 g_free(msgformat);
2593 if (msgr_value) {
2594 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2595 g_free(msgr_value);
2596 } else {
2597 msgr = g_strdup("");
2600 tmp = get_contact(sip);
2601 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2602 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2603 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2604 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2605 tmp, msgr);
2606 g_free(tmp);
2607 g_free(msgr);
2609 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2610 g_free(msgtext);
2611 g_free(hdr);
2612 g_free(fullto);
2616 static void
2617 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2619 GSList *entry = session->outgoing_message_queue;
2621 if (session->outgoing_invite) return; //do not send messages until INVITE responded.
2623 while (entry) {
2624 char *key = g_strdup_printf("<%s><%d><MESSAGE>", session->dialog->callid, (session->dialog->cseq) + 1);
2625 char *queued_msg = entry->data;
2626 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2627 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2628 key, g_hash_table_size(session->unconfirmed_messages));
2629 g_free(key);
2630 sipe_send_message(sip, session, queued_msg);
2631 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2632 g_free(queued_msg);
2636 static void
2637 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2639 GSList *hdr = msg->headers;
2640 struct siphdrelement *elem;
2641 gchar *contact;
2643 while(hdr)
2645 elem = hdr->data;
2646 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2648 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2649 dialog->routes = g_slist_append(dialog->routes, route);
2651 hdr = g_slist_next(hdr);
2654 if (outgoing)
2656 dialog->routes = g_slist_reverse(dialog->routes);
2659 if (dialog->routes)
2661 dialog->request = dialog->routes->data;
2662 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2665 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2666 dialog->routes = g_slist_append(dialog->routes, contact);
2669 static void
2670 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2672 GSList *hdr = msg->headers;
2673 struct siphdrelement *elem;
2674 while(hdr)
2676 elem = hdr->data;
2677 if(!g_ascii_strcasecmp(elem->name, "Supported")
2678 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2680 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2683 hdr = g_slist_next(hdr);
2687 static void
2688 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2690 gchar *us = outgoing ? "From" : "To";
2691 gchar *them = outgoing ? "To" : "From";
2693 g_free(dialog->callid);
2694 g_free(dialog->ourtag);
2695 g_free(dialog->theirtag);
2697 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2698 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2699 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2700 if (!dialog->theirepid) {
2701 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2702 if (!dialog->theirepid) {
2703 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2707 // Catch a tag on the end of the To Header and get rid of it.
2708 if (strstr(dialog->theirepid, "tag=")) {
2709 dialog->theirepid = strtok(dialog->theirepid, ";");
2712 sipe_get_route_header(msg, dialog, outgoing);
2713 sipe_get_supported_header(msg, dialog, outgoing);
2717 static gboolean
2718 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2720 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2721 struct sip_im_session * session = find_im_session(sip, with);
2722 struct sip_dialog *dialog;
2723 char *cseq;
2724 char *key;
2725 gchar *message;
2727 if (!session) {
2728 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2729 g_free(with);
2730 return FALSE;
2733 dialog = session->dialog;
2734 if (!dialog) {
2735 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2736 g_free(with);
2737 return FALSE;
2740 sipe_parse_dialog(msg, dialog, TRUE);
2742 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2743 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2744 g_free(cseq);
2745 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2747 if (msg->response != 200) {
2748 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2750 sipe_present_message_undelivered_err(with, sip, message);
2751 im_session_destroy(sip, session);
2752 g_free(with);
2753 return FALSE;
2756 dialog->cseq = 0;
2757 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2758 session->outgoing_invite = NULL;
2759 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2760 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2761 if (session->outgoing_message_queue) {
2762 char *queued_msg = session->outgoing_message_queue->data;
2763 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2764 g_free(queued_msg);
2768 sipe_im_process_queue(sip, session);
2770 g_hash_table_remove(session->unconfirmed_messages, key);
2771 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2772 key, g_hash_table_size(session->unconfirmed_messages));
2774 g_free(key);
2775 g_free(with);
2776 return TRUE;
2780 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session *session, const gchar *msg_body)
2782 gchar *hdr;
2783 gchar *to;
2784 gchar *from;
2785 gchar *contact;
2786 gchar *body;
2787 char *msgformat;
2788 char *msgtext;
2789 char *base64_msg;
2790 char *ms_text_format;
2791 gchar *msgr_value;
2792 gchar *msgr;
2793 char *key;
2795 if (session->dialog) {
2796 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2797 return;
2800 session->dialog = g_new0(struct sip_dialog, 1);
2801 session->dialog->callid = gencallid();
2803 if (!(session->dialog->ourtag)) {
2804 session->dialog->ourtag = gentag();
2808 if (strstr(session->with, "sip:")) {
2809 to = g_strdup(session->with);
2810 } else {
2811 to = g_strdup_printf("sip:%s", session->with);
2814 sipe_parse_html(msg_body, &msgformat, &msgtext);
2815 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2817 msgr_value = sipmsg_get_msgr_string(msgformat);
2818 g_free(msgformat);
2819 msgr = "";
2820 if (msgr_value) {
2821 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2822 g_free(msgr_value);
2825 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2826 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2827 g_free(msgtext);
2828 g_free(msgr);
2829 g_free(base64_msg);
2831 key = g_strdup_printf("<%s><%d><INVITE>", session->dialog->callid, (session->dialog->cseq) + 1);
2832 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2833 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2834 key, g_hash_table_size(session->unconfirmed_messages));
2835 g_free(key);
2837 contact = get_contact(sip);
2838 /* from = g_strdup_printf("sip:%s", sip->username);*/
2839 hdr = g_strdup_printf(
2840 /*"Supported: ms-delayed-accept\r\n"*/
2841 /*"Roster-Manager: <%s>\r\n"*/
2842 /*"EndPoints: <%s>, <%s>\r\n"*/
2843 /*"Supported: com.microsoft.rtc-multiparty\r\n"*/
2844 "Contact: %s\r\n%s"
2845 "Content-Type: application/sdp\r\n",
2846 contact, ms_text_format);
2847 g_free(ms_text_format);
2849 body = g_strdup_printf(
2850 "v=0\r\n"
2851 "o=- 0 0 IN IP4 %s\r\n"
2852 "s=session\r\n"
2853 "c=IN IP4 %s\r\n"
2854 "t=0 0\r\n"
2855 "m=message %d sip null\r\n"
2856 "a=accept-types:text/plain text/html image/gif "
2857 "multipart/alternative application/im-iscomposing+xml\r\n",
2858 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2860 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2861 to, to, hdr, body, session->dialog, process_invite_response);
2863 g_free(to);
2864 /* g_free(from);*/
2865 g_free(body);
2866 g_free(hdr);
2867 g_free(contact);
2870 static void
2871 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2873 if (session) {
2874 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2875 im_session_destroy(sip, session);
2879 static void
2880 sipe_convo_closed(PurpleConnection * gc, const char *who)
2882 struct sipe_account_data *sip = gc->proto_data;
2884 purple_debug_info("sipe", "conversation with %s closed\n", who);
2885 im_session_close(sip, find_im_session(sip, who));
2888 static void
2889 im_session_close_all (struct sipe_account_data *sip)
2891 GSList *entry = sip->im_sessions;
2892 while (entry) {
2893 im_session_close (sip, entry->data);
2894 entry = sip->im_sessions;
2898 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2900 struct sipe_account_data *sip;
2901 gchar *to;
2902 struct sip_im_session *session;
2904 sip = gc->proto_data;
2905 to = g_strdup(who);
2907 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2909 session = find_or_create_im_session(sip, who);
2911 // Queue the message
2912 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
2914 if (session->dialog && session->dialog->callid) {
2915 sipe_im_process_queue(sip, session);
2916 } else if (!session->outgoing_invite) {
2917 // Need to send the INVITE to get the outgoing dialog setup
2918 sipe_invite(sip, session, what);
2921 g_free(to);
2922 return 1;
2925 /* End IM Session (INVITE and MESSAGE methods) */
2927 static unsigned int
2928 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2930 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2931 struct sip_im_session *session;
2933 if (state == PURPLE_NOT_TYPING)
2934 return 0;
2936 session = find_im_session(sip, who);
2938 if (session && session->dialog) {
2939 send_sip_request(gc, "INFO", who, who,
2940 "Content-Type: application/xml\r\n",
2941 SIPE_SEND_TYPING, session->dialog, NULL);
2943 return SIPE_TYPING_SEND_TIMEOUT;
2946 static gboolean resend_timeout(struct sipe_account_data *sip)
2948 GSList *tmp = sip->transactions;
2949 time_t currtime = time(NULL);
2950 while (tmp) {
2951 struct transaction *trans = tmp->data;
2952 tmp = tmp->next;
2953 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2954 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2955 /* TODO 408 */
2956 } else {
2957 if ((currtime - trans->time > 2) && trans->retries == 0) {
2958 trans->retries++;
2959 sendout_sipmsg(sip, trans->msg);
2963 return TRUE;
2966 static void do_reauthenticate_cb(struct sipe_account_data *sip, void *unused)
2968 /* register again when security token expires */
2969 /* we have to start a new authentication as the security token
2970 * is almost expired by sending a not signed REGISTER message */
2971 purple_debug_info("sipe", "do a full reauthentication\n");
2972 sipe_auth_free(&sip->registrar);
2973 sip->registerstatus = 0;
2974 do_register(sip);
2975 sip->reauthenticate_set = FALSE;
2978 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2980 gchar *from;
2981 gchar *contenttype;
2982 gboolean found = FALSE;
2984 from = parse_from(sipmsg_find_header(msg, "From"));
2986 if (!from) return;
2988 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2990 contenttype = sipmsg_find_header(msg, "Content-Type");
2991 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2993 gchar *html = get_html_message(contenttype, msg->body);
2994 serv_got_im(sip->gc, from, html, 0, time(NULL));
2995 g_free(html);
2996 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2997 found = TRUE;
2999 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3000 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3001 xmlnode *state;
3002 gchar *statedata;
3004 if (!isc) {
3005 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3006 return;
3009 state = xmlnode_get_child(isc, "state");
3011 if (!state) {
3012 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3013 xmlnode_free(isc);
3014 return;
3017 statedata = xmlnode_get_data(state);
3018 if (statedata) {
3019 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3020 else serv_got_typing_stopped(sip->gc, from);
3022 g_free(statedata);
3024 xmlnode_free(isc);
3025 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3026 found = TRUE;
3028 if (!found) {
3029 purple_debug_info("sipe", "got unknown mime-type");
3030 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3032 g_free(from);
3035 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3037 gchar *ms_text_format;
3038 gchar *from;
3039 gchar *body;
3040 gchar *newTag = gentag();
3041 struct sip_im_session *session;
3043 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3045 // Only accept text invitations
3046 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3047 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3048 return;
3051 // TODO There *must* be a better way to clean up the To header to add a tag...
3052 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3053 gchar *oldHeader;
3054 gchar *newHeader;
3055 oldHeader = sipmsg_find_header(msg, "To");
3056 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3057 sipmsg_remove_header(msg, "To");
3058 sipmsg_add_header(msg, "To", newHeader);
3059 g_free(newHeader);
3061 from = parse_from(sipmsg_find_header(msg, "From"));
3062 session = find_or_create_im_session (sip, from);
3063 if (session) {
3064 if (session->dialog) {
3065 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3066 } else {
3067 session->dialog = g_new0(struct sip_dialog, 1);
3069 sipe_parse_dialog(msg, session->dialog, FALSE);
3071 // This stuff is the same stuff that happens in the sipe_parse_dialog...
3072 //session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3073 //session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3074 //session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3075 //session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3077 if (!session->dialog->ourtag) {
3078 session->dialog->ourtag = newTag;
3079 newTag = NULL;
3082 } else {
3083 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3085 g_free(newTag);
3087 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3088 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3089 if (ms_text_format) {
3090 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3092 gchar *html = get_html_message(ms_text_format, NULL);
3093 if (html) {
3094 serv_got_im(sip->gc, from, html, 0, time(NULL));
3095 g_free(html);
3096 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3100 g_free(from);
3102 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3103 sipmsg_remove_header(msg, "Ms-Text-Format");
3104 sipmsg_remove_header(msg, "EndPoints");
3105 sipmsg_remove_header(msg, "User-Agent");
3106 sipmsg_remove_header(msg, "Roster-Manager");
3107 sipmsg_remove_header(msg, "P-Asserted-Identity");
3109 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3110 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3112 body = g_strdup_printf(
3113 "v=0\r\n"
3114 "o=- 0 0 IN IP4 %s\r\n"
3115 "s=session\r\n"
3116 "c=IN IP4 %s\r\n"
3117 "t=0 0\r\n"
3118 "m=message %d sip sip:%s\r\n"
3119 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3120 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3121 sip->realport, sip->username);
3122 send_sip_response(sip->gc, msg, 200, "OK", body);
3123 g_free(body);
3126 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3128 gchar *body;
3130 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3131 sipmsg_remove_header(msg, "EndPoints");
3132 sipmsg_remove_header(msg, "User-Agent");
3134 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3135 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3137 body = g_strdup_printf(
3138 "v=0\r\n"
3139 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3140 "s=session\r\n"
3141 "c=IN IP4 0.0.0.0\r\n"
3142 "t=0 0\r\n"
3143 "m=message %d sip sip:%s\r\n"
3144 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3145 sip->realport, sip->username);
3146 send_sip_response(sip->gc, msg, 200, "OK", body);
3147 g_free(body);
3150 static void sipe_connection_cleanup(struct sipe_account_data *);
3151 static void create_connection(struct sipe_account_data *, gchar *, int);
3153 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3155 gchar *tmp;
3156 const gchar *expires_header;
3157 int expires, i;
3158 GSList *hdr = msg->headers;
3159 GSList *entry;
3160 struct siphdrelement *elem;
3162 expires_header = sipmsg_find_header(msg, "Expires");
3163 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3164 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3166 switch (msg->response) {
3167 case 200:
3168 if (expires == 0) {
3169 sip->registerstatus = 0;
3170 } else {
3171 gchar *contact_hdr = NULL;
3172 gchar *gruu = NULL;
3173 gchar *epid;
3174 gchar *uuid;
3175 gchar *timeout;
3177 if (!sip->reregister_set) {
3178 gchar *action_name = g_strdup_printf("<%s>", "registration");
3179 sipe_schedule_action(action_name, expires, do_register_cb, sip, NULL);
3180 g_free(action_name);
3181 sip->reregister_set = TRUE;
3184 sip->registerstatus = 3;
3186 if (!sip->reauthenticate_set) {
3187 /* we have to reauthenticate as our security token expires
3188 after eight hours (be five minutes early) */
3189 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3190 guint reauth_timeout = (8 * 3600) - 360;
3191 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, sip, NULL);
3192 g_free(action_name);
3193 sip->reauthenticate_set = TRUE;
3196 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3198 epid = get_epid(sip);
3199 uuid = generateUUIDfromEPID(epid);
3200 g_free(epid);
3202 // There can be multiple Contact headers (one per location where the user is logged in) so
3203 // make sure to only get the one for this uuid
3204 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3205 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3206 if (valid_contact) {
3207 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3208 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3209 g_free(valid_contact);
3210 break;
3211 } else {
3212 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3215 g_free(uuid);
3217 g_free(sip->contact);
3218 if(gruu) {
3219 sip->contact = g_strdup_printf("<%s>", gruu);
3220 g_free(gruu);
3221 } else {
3222 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3223 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);
3225 sip->msrtc_event_categories = FALSE;
3226 sip->batched_support = FALSE;
3228 while(hdr)
3230 elem = hdr->data;
3231 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3232 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3233 sip->msrtc_event_categories = TRUE;
3234 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3236 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3237 sip->batched_support = TRUE;
3238 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3241 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3242 gchar **caps = g_strsplit(elem->value,",",0);
3243 i = 0;
3244 while (caps[i]) {
3245 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3246 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3247 i++;
3249 g_strfreev(caps);
3251 hdr = g_slist_next(hdr);
3254 if (!sip->subscribed) { //do it just once, not every re-register
3255 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3256 sipe_options_request(sip, sip->sipdomain);
3258 entry = sip->allow_events;
3259 while (entry) {
3260 tmp = entry->data;
3261 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3262 sipe_subscribe_roaming_contacts(sip, msg);
3264 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3265 sipe_subscribe_roaming_acl(sip, msg);
3267 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3268 sipe_subscribe_roaming_self(sip, msg);
3270 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3271 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3272 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3273 sipe_subscribe_roaming_provisioning(sip, msg);
3275 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3276 sipe_subscribe_presence_wpending(sip, msg);
3278 entry = entry->next;
3280 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3281 sip->subscribed = TRUE;
3284 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3285 "timeout=", ";", NULL);
3286 if (timeout != NULL) {
3287 sscanf(timeout, "%u", &sip->keepalive_timeout);
3288 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3289 sip->keepalive_timeout);
3290 g_free(timeout);
3291 } else {
3292 sip->keepalive_timeout = 300;
3295 // Should we remove the transaction here?
3296 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3297 transactions_remove(sip, tc);
3299 break;
3300 case 301:
3302 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3304 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3305 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3306 gchar **tmp;
3307 gchar *hostname;
3308 int port = 0;
3309 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3310 int i = 1;
3312 tmp = g_strsplit(parts[0], ":", 0);
3313 hostname = g_strdup(tmp[0]);
3314 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3315 g_strfreev(tmp);
3317 while (parts[i]) {
3318 tmp = g_strsplit(parts[i], "=", 0);
3319 if (tmp[1]) {
3320 if (g_strcasecmp("transport", tmp[0]) == 0) {
3321 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3322 transport = SIPE_TRANSPORT_TCP;
3323 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3324 transport = SIPE_TRANSPORT_UDP;
3328 g_strfreev(tmp);
3329 i++;
3331 g_strfreev(parts);
3333 /* Close old connection */
3334 sipe_connection_cleanup(sip);
3336 /* Create new connection */
3337 sip->transport = transport;
3338 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3339 hostname, port, TRANSPORT_DESCRIPTOR);
3340 create_connection(sip, hostname, port);
3342 g_free(redirect);
3344 break;
3345 case 401:
3346 if (sip->registerstatus != 2) {
3347 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3348 if (sip->registrar.retries > 3) {
3349 sip->gc->wants_to_die = TRUE;
3350 purple_connection_error(sip->gc, _("Wrong Password"));
3351 return TRUE;
3353 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3354 tmp = sipmsg_find_auth_header(msg, "NTLM");
3355 } else {
3356 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3358 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3359 fill_auth(sip, tmp, &sip->registrar);
3360 sip->registerstatus = 2;
3361 if (sip->account->disconnecting) {
3362 do_register_exp(sip, 0);
3363 } else {
3364 do_register(sip);
3367 break;
3368 case 403:
3370 gchar *warning = sipmsg_find_header(msg, "Warning");
3371 if (warning != NULL) {
3372 /* Example header:
3373 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3375 gchar **tmp = g_strsplit(warning, "\"", 0);
3376 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3377 g_strfreev(tmp);
3378 } else {
3379 warning = g_strdup(_("You have been rejected by the server"));
3382 sip->gc->wants_to_die = TRUE;
3383 purple_connection_error(sip->gc, warning);
3384 g_free(warning);
3385 return TRUE;
3387 break;
3388 case 404:
3390 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3391 if (warning != NULL) {
3392 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3393 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3394 g_free(reason);
3395 } else {
3396 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3399 sip->gc->wants_to_die = TRUE;
3400 purple_connection_error(sip->gc, warning);
3401 g_free(warning);
3402 return TRUE;
3404 break;
3405 case 503:
3407 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3408 if (warning != NULL) {
3409 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3410 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3411 g_free(reason);
3412 } else {
3413 warning = g_strdup(_("Service unavailable: no reason given"));
3416 sip->gc->wants_to_die = TRUE;
3417 purple_connection_error(sip->gc, warning);
3418 g_free(warning);
3419 return TRUE;
3421 break;
3423 return TRUE;
3426 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3428 const char *uri;
3429 xmlnode *xn_categories;
3430 xmlnode *xn_category;
3431 xmlnode *xn_node;
3432 const char *activity = NULL;
3434 xn_categories = xmlnode_from_str(data, len);
3435 uri = xmlnode_get_attrib(xn_categories, "uri");
3437 for (xn_category = xmlnode_get_child(xn_categories, "category");
3438 xn_category ;
3439 xn_category = xmlnode_get_next_twin(xn_category) )
3441 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3443 if (!strcmp(attrVar, "note"))
3445 char *note;
3446 struct sipe_buddy *sbuddy;
3447 xn_node = xmlnode_get_child(xn_category, "note");
3448 if (!xn_node) continue;
3449 xn_node = xmlnode_get_child(xn_node, "body");
3450 if (!xn_node) continue;
3452 note = xmlnode_get_data(xn_node);
3454 if(uri){
3455 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3457 if (sbuddy && note)
3459 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note);
3460 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3461 sbuddy->annotation = g_strdup(note);
3463 if(note)
3464 g_free(note);
3466 else if(!strcmp(attrVar, "state"))
3468 char *data;
3469 int avail;
3470 xn_node = xmlnode_get_child(xn_category, "state");
3471 if (!xn_node) continue;
3472 xn_node = xmlnode_get_child(xn_node, "availability");
3473 if (!xn_node) continue;
3475 data = xmlnode_get_data(xn_node);
3476 avail = atoi(data);
3477 g_free(data);
3479 if (avail < 3000)
3480 activity = SIPE_STATUS_ID_UNKNOWN;
3481 else if (avail < 4500)
3482 activity = SIPE_STATUS_ID_AVAILABLE;
3483 else if (avail < 6000)
3484 activity = SIPE_STATUS_ID_BRB;
3485 else if (avail < 7500)
3486 activity = SIPE_STATUS_ID_ONPHONE;
3487 else if (avail < 9000)
3488 activity = SIPE_STATUS_ID_BUSY;
3489 else if (avail < 12000)
3490 activity = SIPE_STATUS_ID_DND;
3491 else if (avail < 18000)
3492 activity = SIPE_STATUS_ID_AWAY;
3493 else
3494 activity = SIPE_STATUS_ID_OFFLINE;
3497 if(activity) {
3498 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
3499 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3502 xmlnode_free(xn_categories);
3505 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3507 xmlnode *xn_list;
3508 xmlnode *xn_resource;
3510 xn_list = xmlnode_from_str(data, len);
3512 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3513 xn_resource;
3514 xn_resource = xmlnode_get_next_twin(xn_resource) )
3516 const char *uri, *state;
3517 xmlnode *xn_instance;
3519 xn_instance = xmlnode_get_child(xn_resource, "instance");
3520 if (!xn_instance) continue;
3522 uri = xmlnode_get_attrib(xn_resource, "uri");
3523 state = xmlnode_get_attrib(xn_instance, "state");
3524 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
3526 if (strstr(state, "resubscribe")) {
3527 struct sipe_buddy *sbuddy;
3528 sipe_subscribe_presence_single(sip, (void *) uri);
3529 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3530 if (sbuddy) {
3531 sbuddy->resubscribed = TRUE;
3536 xmlnode_free(xn_list);
3539 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3541 const gchar *uri;
3542 gchar *getbasic;
3543 gchar *activity = NULL;
3544 xmlnode *pidf;
3545 xmlnode *basicstatus = NULL, *tuple, *status;
3546 gboolean isonline = FALSE;
3547 xmlnode *display_name_node;
3549 pidf = xmlnode_from_str(data, len);
3550 if (!pidf) {
3551 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3552 return;
3555 uri = xmlnode_get_attrib(pidf, "entity");
3557 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3559 if ((status = xmlnode_get_child(tuple, "status"))) {
3560 basicstatus = xmlnode_get_child(status, "basic");
3564 if (!basicstatus) {
3565 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3566 xmlnode_free(pidf);
3567 return;
3570 getbasic = xmlnode_get_data(basicstatus);
3571 if (!getbasic) {
3572 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3573 xmlnode_free(pidf);
3574 return;
3577 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3578 if (strstr(getbasic, "open")) {
3579 isonline = TRUE;
3581 g_free(getbasic);
3583 display_name_node = xmlnode_get_child(pidf, "display-name");
3584 // updating display name if alias was just URI
3585 if (display_name_node) {
3586 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3587 GSList *entry = buddies;
3588 PurpleBuddy *p_buddy;
3589 char * display_name = xmlnode_get_data(display_name_node);
3591 while (entry) {
3592 const char *server_alias;
3593 char *alias;
3595 p_buddy = entry->data;
3597 alias = (char *)purple_buddy_get_alias(p_buddy);
3598 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3599 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3600 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3601 purple_blist_alias_buddy(p_buddy, display_name);
3603 g_free(alias);
3605 server_alias = purple_buddy_get_server_alias(p_buddy);
3606 if (display_name &&
3607 ( (server_alias && strcmp(display_name, server_alias))
3608 || !server_alias || strlen(server_alias) == 0 )
3610 purple_blist_server_alias_buddy(p_buddy, display_name);
3613 entry = entry->next;
3615 g_free(display_name);
3618 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3619 if ((status = xmlnode_get_child(tuple, "status"))) {
3620 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3621 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3622 activity = xmlnode_get_data(basicstatus);
3623 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3629 if (isonline) {
3630 const gchar * status_id = NULL;
3631 if (activity) {
3632 if (strstr(activity, "busy")) {
3633 status_id = SIPE_STATUS_ID_BUSY;
3634 } else if (strstr(activity, "away")) {
3635 status_id = SIPE_STATUS_ID_AWAY;
3639 if (!status_id) {
3640 status_id = SIPE_STATUS_ID_AVAILABLE;
3643 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3644 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3645 } else {
3646 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
3649 g_free(activity);
3650 xmlnode_free(pidf);
3653 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3655 const char *availability;
3656 const char *activity;
3657 const char *display_name = NULL;
3658 const char *activity_name = NULL;
3659 const char *name;
3660 char *uri;
3661 int avl;
3662 int act;
3663 struct sipe_buddy *sbuddy;
3665 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3667 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3668 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3669 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3670 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3671 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3672 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3673 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
3674 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
3676 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3677 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3678 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3679 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3680 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3681 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3683 name = xmlnode_get_attrib(xn_presentity, "uri");
3684 uri = g_strdup_printf("sip:%s", name);
3685 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3686 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3688 // updating display name if alias was just URI
3689 if (xn_display_name) {
3690 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3691 GSList *entry = buddies;
3692 PurpleBuddy *p_buddy;
3693 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3695 while (entry) {
3696 const char *email_str, *server_alias;
3698 p_buddy = entry->data;
3700 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3701 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3702 purple_blist_alias_buddy(p_buddy, display_name);
3705 server_alias = purple_buddy_get_server_alias(p_buddy);
3706 if (display_name &&
3707 ( (server_alias && strcmp(display_name, server_alias))
3708 || !server_alias || strlen(server_alias) == 0 )
3710 purple_blist_server_alias_buddy(p_buddy, display_name);
3713 if (email) {
3714 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3715 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3716 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3720 entry = entry->next;
3724 avl = atoi(availability);
3725 act = atoi(activity);
3727 if(sip->msrtc_event_categories){
3728 if (act == 100 && avl == 0)
3729 activity_name = SIPE_STATUS_ID_OFFLINE;
3730 else if (act == 100 && avl == 300)
3731 activity_name = SIPE_STATUS_ID_AWAY;
3732 else if (act == 300 && avl == 300)
3733 activity_name = SIPE_STATUS_ID_BRB;
3734 else if (act == 400 && avl == 300)
3735 activity_name = SIPE_STATUS_ID_AVAILABLE;
3736 else if (act == 500 && act == 300)
3737 activity_name = SIPE_STATUS_ID_ONPHONE;
3738 else if (act == 600 && avl == 300)
3739 activity_name = SIPE_STATUS_ID_BUSY;
3740 else if (act == 0 && avl == 0){ //MSRTC elements are zero
3741 if(avail){ //Check for LegacyInterop elements
3742 avl = atoi(avail);
3743 if(avl == 18500)
3744 activity_name = SIPE_STATUS_ID_OFFLINE;
3745 else if (avl == 3500)
3746 activity_name = SIPE_STATUS_ID_AVAILABLE;
3747 else if (avl == 15500)
3748 activity_name = SIPE_STATUS_ID_AWAY;
3749 else if (avl == 6500)
3750 activity_name = SIPE_STATUS_ID_BUSY;
3751 else if (avl == 12500)
3752 activity_name = SIPE_STATUS_ID_BRB;
3757 if(activity_name == NULL){
3758 if (act <= 100)
3759 activity_name = SIPE_STATUS_ID_AWAY;
3760 else if (act <= 150)
3761 activity_name = SIPE_STATUS_ID_LUNCH;
3762 else if (act <= 300)
3763 activity_name = SIPE_STATUS_ID_BRB;
3764 else if (act <= 400)
3765 activity_name = SIPE_STATUS_ID_AVAILABLE;
3766 else if (act <= 500)
3767 activity_name = SIPE_STATUS_ID_ONPHONE;
3768 else if (act <= 600)
3769 activity_name = SIPE_STATUS_ID_BUSY;
3770 else
3771 activity_name = SIPE_STATUS_ID_AVAILABLE;
3773 if (avl == 0)
3774 activity_name = SIPE_STATUS_ID_OFFLINE;
3777 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3778 if (sbuddy)
3780 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3781 sbuddy->annotation = NULL;
3782 if (note) { sbuddy->annotation = g_strdup(note); }
3784 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3785 sbuddy->device_name = NULL;
3786 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3789 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3790 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3791 g_free(note);
3792 xmlnode_free(xn_presentity);
3793 g_free(uri);
3796 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3798 char *ctype = sipmsg_find_header(msg, "Content-Type");
3800 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3802 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3803 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3805 const char *content = msg->body;
3806 unsigned length = msg->bodylen;
3807 PurpleMimeDocument *mime = NULL;
3809 if (strstr(ctype, "multipart"))
3811 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3812 const char *content_type;
3813 GList* parts;
3814 mime = purple_mime_document_parse(doc);
3815 parts = purple_mime_document_get_parts(mime);
3816 while(parts) {
3817 content = purple_mime_part_get_data(parts->data);
3818 length = purple_mime_part_get_length(parts->data);
3819 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3820 if(content_type && strstr(content_type,"application/rlmi+xml"))
3822 process_incoming_notify_rlmi_resub(sip, content, length);
3824 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3826 process_incoming_notify_msrtc(sip, content, length);
3828 else
3830 process_incoming_notify_rlmi(sip, content, length);
3832 parts = parts->next;
3834 g_free(doc);
3836 if (mime)
3838 purple_mime_document_free(mime);
3841 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3843 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3845 else if(strstr(ctype, "application/rlmi+xml"))
3847 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3850 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3852 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3854 else
3856 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3861 * Dispatcher for all incoming subscription information
3862 * whether it comes from NOTIFY, BENOTIFY requests or
3863 * piggy-backed to subscription's OK responce.
3865 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3866 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3868 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3870 gchar *event = sipmsg_find_header(msg, "Event");
3871 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3872 int timeout = 0;
3874 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3875 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3877 if (!request)
3879 const gchar *expires_header;
3880 expires_header = sipmsg_find_header(msg, "Expires");
3881 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3882 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
3883 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
3886 if (!subscription_state || strstr(subscription_state, "active"))
3888 if (event && !g_ascii_strcasecmp(event, "presence"))
3890 sipe_process_presence(sip, msg);
3892 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3894 sipe_process_roaming_contacts(sip, msg, NULL);
3896 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
3898 sipe_process_roaming_self(sip, msg);
3900 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3902 sipe_process_roaming_acl(sip, msg);
3904 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3906 sipe_process_presence_wpending(sip, msg);
3908 else
3910 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3914 //The server sends a (BE)NOTIFY with the status 'terminated'
3915 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
3916 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3917 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3918 g_free(from);
3921 if (timeout && event) {// For LSC 2005 and OCS 2007
3922 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
3923 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
3925 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
3926 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, sip, msg);
3927 g_free(action_name);
3929 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
3930 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
3932 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
3933 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, sip, msg);
3934 g_free(action_name);
3936 else*/
3937 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
3938 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3940 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3941 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, sip, NULL);
3942 g_free(action_name);
3944 else if (!g_ascii_strcasecmp(event, "presence") &&
3945 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3947 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3948 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
3949 if(sip->batched_support) {
3950 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
3951 if(!g_ascii_strcasecmp(who, my_self)){
3952 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, sip, NULL);
3953 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
3954 g_free(who); /* unused */
3956 else {
3957 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, sip, who);
3958 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who,timeout);
3960 g_free(my_self);
3962 else {
3963 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, sip, who);
3964 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
3966 g_free(action_name);
3967 /* "who" will be freed by the action we just scheduled */
3971 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
3973 sipe_process_registration_notify(sip, msg);
3976 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3977 if (request && !benotify)
3979 sipmsg_remove_header(msg, "Expires");
3980 sipmsg_remove_header(msg, "subscription-state");
3981 sipmsg_remove_header(msg, "Event");
3982 sipmsg_remove_header(msg, "Require");
3983 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3988 * unused. Needed?
3990 static gchar* gen_xpidf(struct sipe_account_data *sip)
3992 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3993 "<presence>\r\n"
3994 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3995 "<display name=\"sip:%s\"/>\r\n"
3996 "<atom id=\"1234\">\r\n"
3997 "<address uri=\"sip:%s\">\r\n"
3998 "<status status=\"%s\"/>\r\n"
3999 "</address>\r\n"
4000 "</atom>\r\n"
4001 "</presence>\r\n",
4002 sip->username,
4003 sip->username,
4004 sip->username,
4005 sip->status);
4006 return doc;
4011 static gchar* gen_pidf(struct sipe_account_data *sip)
4013 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4014 "<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"
4015 "<tuple id=\"0\">\r\n"
4016 "<status>\r\n"
4017 "<basic>open</basic>\r\n"
4018 "<ep:activities>\r\n"
4019 " <ep:activity>%s</ep:activity>\r\n"
4020 "</ep:activities>"
4021 "</status>\r\n"
4022 "</tuple>\r\n"
4023 "<ci:display-name>%s</ci:display-name>\r\n"
4024 "</presence>",
4025 sip->username,
4026 sip->status,
4027 sip->username);
4028 return doc;
4032 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4034 int availability = 300; // online
4035 int activity = 400; // Available
4036 gchar *name;
4037 gchar *body;
4038 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4039 activity = 100;
4040 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4041 activity = 150;
4042 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4043 activity = 300;
4044 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4045 activity = 400;
4046 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4047 activity = 500;
4048 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4049 activity = 600;
4050 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4051 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4052 availability = 0; // offline
4053 activity = 100;
4054 } else {
4055 activity = 400; // available
4058 name = g_strdup_printf("sip: sip:%s", sip->username);
4059 //@TODO: send user data - state; add hostname in upper case
4060 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4061 send_soap_request_with_cb(sip, body, NULL , NULL);
4062 g_free(name);
4063 g_free(body);
4066 static gboolean
4067 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4069 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4070 if (msg->response == 200) {
4071 sip->status_version = 0;
4072 send_presence_status(sip);
4074 return TRUE;
4077 static gboolean
4078 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4080 if (msg->response == 409) {
4081 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4082 // TODO need to parse the version #'s?
4083 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4084 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4085 gchar *tmp;
4086 gchar *hdr;
4088 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4090 tmp = get_contact(sip);
4091 hdr = g_strdup_printf("Contact: %s\r\n"
4092 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4094 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4096 g_free(tmp);
4097 g_free(hdr);
4098 g_free(uri);
4099 g_free(doc);
4101 return TRUE;
4104 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4106 int code;
4107 gchar *uri;
4108 gchar *doc;
4109 gchar *tmp;
4110 gchar *hdr;
4111 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4112 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4113 code = 12000;
4114 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4115 code = 9000;
4116 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4117 code = 7500;
4118 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4119 code = 6000;
4120 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4121 code = 4500;
4122 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4123 code = 3000;
4124 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4125 code = 0;
4126 } else {
4127 // Offline or invisible
4128 code = 18000;
4131 uri = g_strdup_printf("sip:%s", sip->username);
4132 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4133 sip->status_version, code,
4134 sip->status_version, code,
4135 sip->status_version, note ? note : "",
4136 sip->status_version, note ? note : "",
4137 sip->status_version, note ? note : ""
4139 sip->status_version++;
4141 tmp = get_contact(sip);
4142 hdr = g_strdup_printf("Contact: %s\r\n"
4143 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4145 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4147 g_free(tmp);
4148 g_free(hdr);
4149 g_free(uri);
4150 g_free(doc);
4153 static void send_presence_status(struct sipe_account_data *sip)
4155 PurpleStatus * status = purple_account_get_active_status(sip->account);
4156 const gchar *note;
4157 if (!status) return;
4159 note = purple_status_get_attr_string(status, "message");
4161 if(sip->msrtc_event_categories){
4162 send_presence_category_publish(sip, note);
4163 } else {
4164 send_presence_soap(sip, note);
4168 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4170 gboolean found = FALSE;
4171 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4172 if (msg->response == 0) { /* request */
4173 if (!strcmp(msg->method, "MESSAGE")) {
4174 process_incoming_message(sip, msg);
4175 found = TRUE;
4176 } else if (!strcmp(msg->method, "NOTIFY")) {
4177 purple_debug_info("sipe","send->process_incoming_notify\n");
4178 process_incoming_notify(sip, msg, TRUE, FALSE);
4179 found = TRUE;
4180 } else if (!strcmp(msg->method, "BENOTIFY")) {
4181 purple_debug_info("sipe","send->process_incoming_benotify\n");
4182 process_incoming_notify(sip, msg, TRUE, TRUE);
4183 found = TRUE;
4184 } else if (!strcmp(msg->method, "INVITE")) {
4185 process_incoming_invite(sip, msg);
4186 found = TRUE;
4187 } else if (!strcmp(msg->method, "OPTIONS")) {
4188 process_incoming_options(sip, msg);
4189 found = TRUE;
4190 } else if (!strcmp(msg->method, "INFO")) {
4191 // TODO needs work
4192 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4193 if (from) {
4194 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4196 g_free(from);
4197 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4198 found = TRUE;
4199 } else if (!strcmp(msg->method, "ACK")) {
4200 // ACK's don't need any response
4201 found = TRUE;
4202 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4203 // LCS 2005 sends us these - just respond 200 OK
4204 found = TRUE;
4205 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4206 } else if (!strcmp(msg->method, "BYE")) {
4207 struct sip_im_session *session;
4208 gchar *from;
4209 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4211 from = parse_from(sipmsg_find_header(msg, "From"));
4212 session = find_im_session (sip, from);
4213 g_free(from);
4215 if (session) {
4216 // TODO Let the user know the other user left the conversation?
4217 im_session_destroy(sip, session);
4220 found = TRUE;
4221 } else {
4222 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4224 } else { /* response */
4225 struct transaction *trans = transactions_find(sip, msg);
4226 if (trans) {
4227 if (msg->response == 407) {
4228 gchar *resend, *auth, *ptmp;
4230 if (sip->proxy.retries > 30) return;
4231 sip->proxy.retries++;
4232 /* do proxy authentication */
4234 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4236 fill_auth(sip, ptmp, &sip->proxy);
4237 auth = auth_header(sip, &sip->proxy, trans->msg);
4238 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4239 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4240 g_free(auth);
4241 resend = sipmsg_to_string(trans->msg);
4242 /* resend request */
4243 sendout_pkt(sip->gc, resend);
4244 g_free(resend);
4245 } else {
4246 if (msg->response == 100 || msg->response == 180) {
4247 /* ignore provisional response */
4248 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4249 } else {
4250 sip->proxy.retries = 0;
4251 if (!strcmp(trans->msg->method, "REGISTER")) {
4252 if (msg->response == 401)
4254 sip->registrar.retries++;
4255 sip->registrar.expires = 0;
4257 else
4259 sip->registrar.retries = 0;
4261 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4262 } else {
4263 if (msg->response == 401) {
4264 gchar *resend, *auth, *ptmp;
4266 if (sip->registrar.retries > 4) return;
4267 sip->registrar.retries++;
4269 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4270 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4271 } else {
4272 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4275 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4277 fill_auth(sip, ptmp, &sip->registrar);
4278 auth = auth_header(sip, &sip->registrar, trans->msg);
4279 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4280 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
4282 //sipmsg_remove_header(trans->msg, "Authorization");
4283 //sipmsg_add_header(trans->msg, "Authorization", auth);
4284 g_free(auth);
4285 resend = sipmsg_to_string(trans->msg);
4286 /* resend request */
4287 sendout_pkt(sip->gc, resend);
4288 g_free(resend);
4292 if (trans->callback) {
4293 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4294 /* call the callback to process response*/
4295 (trans->callback)(sip, msg, trans);
4297 /* Not sure if this is needed or what needs to be done
4298 but transactions seem to be removed prematurely so
4299 this only removes them if the response is 200 OK */
4300 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4301 /*Has a bug and it's unneccesary*/
4302 /*transactions_remove(sip, trans);*/
4306 found = TRUE;
4307 } else {
4308 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4311 if (!found) {
4312 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4316 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4318 char *cur;
4319 char *dummy;
4320 struct sipmsg *msg;
4321 int restlen;
4322 cur = conn->inbuf;
4324 /* according to the RFC remove CRLF at the beginning */
4325 while (*cur == '\r' || *cur == '\n') {
4326 cur++;
4328 if (cur != conn->inbuf) {
4329 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4330 conn->inbufused = strlen(conn->inbuf);
4333 /* Received a full Header? */
4334 sip->processing_input = TRUE;
4335 while (sip->processing_input &&
4336 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4337 time_t currtime = time(NULL);
4338 cur += 2;
4339 cur[0] = '\0';
4340 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4341 msg = sipmsg_parse_header(conn->inbuf);
4342 cur[0] = '\r';
4343 cur += 2;
4344 restlen = conn->inbufused - (cur - conn->inbuf);
4345 if (restlen >= msg->bodylen) {
4346 dummy = g_malloc(msg->bodylen + 1);
4347 memcpy(dummy, cur, msg->bodylen);
4348 dummy[msg->bodylen] = '\0';
4349 msg->body = dummy;
4350 cur += msg->bodylen;
4351 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4352 conn->inbufused = strlen(conn->inbuf);
4353 } else {
4354 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4355 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4356 sipmsg_free(msg);
4357 return;
4360 /*if (msg->body) {
4361 purple_debug_info("sipe", "body:\n%s", msg->body);
4364 // Verify the signature before processing it
4365 if (sip->registrar.ntlm_key) {
4366 struct sipmsg_breakdown msgbd;
4367 gchar *signature_input_str;
4368 gchar *signature = NULL;
4369 gchar *rspauth;
4370 msgbd.msg = msg;
4371 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4372 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4373 if (signature_input_str != NULL) {
4374 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4376 g_free(signature_input_str);
4378 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4380 if (signature != NULL) {
4381 if (rspauth != NULL) {
4382 if (purple_ntlm_verify_signature (signature, rspauth)) {
4383 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4384 process_input_message(sip, msg);
4385 } else {
4386 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4387 purple_connection_error(sip->gc, _("Invalid message signature received"));
4388 sip->gc->wants_to_die = TRUE;
4390 } else if (msg->response == 401) {
4391 purple_connection_error(sip->gc, _("Wrong Password"));
4392 sip->gc->wants_to_die = TRUE;
4394 g_free(signature);
4397 g_free(rspauth);
4398 sipmsg_breakdown_free(&msgbd);
4399 } else {
4400 process_input_message(sip, msg);
4403 sipmsg_free(msg);
4407 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4409 PurpleConnection *gc = data;
4410 struct sipe_account_data *sip = gc->proto_data;
4411 struct sipmsg *msg;
4412 int len;
4413 time_t currtime;
4415 static char buffer[65536];
4416 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4417 buffer[len] = '\0';
4418 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4419 msg = sipmsg_parse_msg(buffer);
4420 if (msg) process_input_message(sip, msg);
4424 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4426 struct sipe_account_data *sip = gc->proto_data;
4427 PurpleSslConnection *gsc = sip->gsc;
4429 purple_debug_error("sipe", "%s",debug);
4430 purple_connection_error(gc, msg);
4432 /* Invalidate this connection. Next send will open a new one */
4433 if (gsc) {
4434 connection_remove(sip, gsc->fd);
4435 purple_ssl_close(gsc);
4437 sip->gsc = NULL;
4438 sip->fd = -1;
4441 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4443 PurpleConnection *gc = data;
4444 struct sipe_account_data *sip;
4445 struct sip_connection *conn;
4446 int readlen, len;
4447 gboolean firstread = TRUE;
4449 /* NOTE: This check *IS* necessary */
4450 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4451 purple_ssl_close(gsc);
4452 return;
4455 sip = gc->proto_data;
4456 conn = connection_find(sip, gsc->fd);
4457 if (conn == NULL) {
4458 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4459 gc->wants_to_die = TRUE;
4460 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4461 return;
4464 /* Read all available data from the SSL connection */
4465 do {
4466 /* Increase input buffer size as needed */
4467 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4468 conn->inbuflen += SIMPLE_BUF_INC;
4469 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4470 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4473 /* Try to read as much as there is space left in the buffer */
4474 readlen = conn->inbuflen - conn->inbufused - 1;
4475 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4477 if (len < 0 && errno == EAGAIN) {
4478 /* Try again later */
4479 return;
4480 } else if (len < 0) {
4481 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4482 return;
4483 } else if (firstread && (len == 0)) {
4484 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4485 return;
4488 conn->inbufused += len;
4489 firstread = FALSE;
4491 /* Equivalence indicates that there is possibly more data to read */
4492 } while (len == readlen);
4494 conn->inbuf[conn->inbufused] = '\0';
4495 process_input(sip, conn);
4499 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4501 PurpleConnection *gc = data;
4502 struct sipe_account_data *sip = gc->proto_data;
4503 int len;
4504 struct sip_connection *conn = connection_find(sip, source);
4505 if (!conn) {
4506 purple_debug_error("sipe", "Connection not found!\n");
4507 return;
4510 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4511 conn->inbuflen += SIMPLE_BUF_INC;
4512 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4515 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4517 if (len < 0 && errno == EAGAIN)
4518 return;
4519 else if (len <= 0) {
4520 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4521 connection_remove(sip, source);
4522 if (sip->fd == source) sip->fd = -1;
4523 return;
4526 conn->inbufused += len;
4527 conn->inbuf[conn->inbufused] = '\0';
4529 process_input(sip, conn);
4532 /* Callback for new connections on incoming TCP port */
4533 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4535 PurpleConnection *gc = data;
4536 struct sipe_account_data *sip = gc->proto_data;
4537 struct sip_connection *conn;
4539 int newfd = accept(source, NULL, NULL);
4541 conn = connection_create(sip, newfd);
4543 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4546 static void login_cb(gpointer data, gint source, const gchar *error_message)
4548 PurpleConnection *gc = data;
4549 struct sipe_account_data *sip;
4550 struct sip_connection *conn;
4552 if (!PURPLE_CONNECTION_IS_VALID(gc))
4554 if (source >= 0)
4555 close(source);
4556 return;
4559 if (source < 0) {
4560 purple_connection_error(gc, _("Could not connect"));
4561 return;
4564 sip = gc->proto_data;
4565 sip->fd = source;
4566 sip->last_keepalive = time(NULL);
4568 conn = connection_create(sip, source);
4570 do_register(sip);
4572 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4575 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4577 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4578 if (sip == NULL) return;
4580 do_register(sip);
4583 static guint sipe_ht_hash_nick(const char *nick)
4585 char *lc = g_utf8_strdown(nick, -1);
4586 guint bucket = g_str_hash(lc);
4587 g_free(lc);
4589 return bucket;
4592 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4594 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4597 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4599 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4601 sip->listen_data = NULL;
4603 if (listenfd == -1) {
4604 purple_connection_error(sip->gc, _("Could not create listen socket"));
4605 return;
4608 sip->fd = listenfd;
4610 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4611 sip->listenfd = sip->fd;
4613 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4615 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4616 do_register(sip);
4619 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4621 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4622 int addr_size;
4624 sip->query_data = NULL;
4626 if (!hosts || !hosts->data) {
4627 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4628 return;
4631 addr_size = GPOINTER_TO_INT(hosts->data);
4632 hosts = g_slist_remove(hosts, hosts->data);
4633 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4634 g_free(hosts->data);
4635 hosts = g_slist_remove(hosts, hosts->data);
4636 while (hosts) {
4637 hosts = g_slist_remove(hosts, hosts->data);
4638 g_free(hosts->data);
4639 hosts = g_slist_remove(hosts, hosts->data);
4642 /* create socket for incoming connections */
4643 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4644 sipe_udp_host_resolved_listen_cb, sip);
4645 if (sip->listen_data == NULL) {
4646 purple_connection_error(sip->gc, _("Could not create listen socket"));
4647 return;
4651 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4652 gpointer data)
4654 PurpleConnection *gc = data;
4655 struct sipe_account_data *sip;
4657 /* If the connection is already disconnected, we don't need to do anything else */
4658 if (!PURPLE_CONNECTION_IS_VALID(gc))
4659 return;
4661 sip = gc->proto_data;
4662 sip->fd = -1;
4663 sip->gsc = NULL;
4665 switch(error) {
4666 case PURPLE_SSL_CONNECT_FAILED:
4667 purple_connection_error(gc, _("Connection Failed"));
4668 break;
4669 case PURPLE_SSL_HANDSHAKE_FAILED:
4670 purple_connection_error(gc, _("SSL Handshake Failed"));
4671 break;
4672 case PURPLE_SSL_CERTIFICATE_INVALID:
4673 purple_connection_error(gc, _("SSL Certificate Invalid"));
4674 break;
4678 static void
4679 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4681 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4682 PurpleProxyConnectData *connect_data;
4684 sip->listen_data = NULL;
4686 sip->listenfd = listenfd;
4687 if (sip->listenfd == -1) {
4688 purple_connection_error(sip->gc, _("Could not create listen socket"));
4689 return;
4692 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4693 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4694 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4695 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4696 sipe_newconn_cb, sip->gc);
4697 purple_debug_info("sipe", "connecting to %s port %d\n",
4698 sip->realhostname, sip->realport);
4699 /* open tcp connection to the server */
4700 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4701 sip->realport, login_cb, sip->gc);
4703 if (connect_data == NULL) {
4704 purple_connection_error(sip->gc, _("Couldn't create socket"));
4709 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4711 PurpleAccount *account = sip->account;
4712 PurpleConnection *gc = sip->gc;
4714 if (purple_account_get_bool(account, "useport", FALSE)) {
4715 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4716 port = purple_account_get_int(account, "port", 0);
4717 } else {
4718 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4721 sip->realhostname = hostname;
4722 sip->realport = port;
4724 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4725 hostname, port);
4727 /* TODO: is there a good default grow size? */
4728 if (sip->transport != SIPE_TRANSPORT_UDP)
4729 sip->txbuf = purple_circ_buffer_new(0);
4731 if (sip->transport == SIPE_TRANSPORT_TLS) {
4732 /* SSL case */
4733 if (!purple_ssl_is_supported()) {
4734 gc->wants_to_die = TRUE;
4735 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4736 return;
4739 purple_debug_info("sipe", "using SSL\n");
4741 sip->gsc = purple_ssl_connect(account, hostname, port,
4742 login_cb_ssl, sipe_ssl_connect_failure, gc);
4743 if (sip->gsc == NULL) {
4744 purple_connection_error(gc, _("Could not create SSL context"));
4745 return;
4747 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4748 /* UDP case */
4749 purple_debug_info("sipe", "using UDP\n");
4751 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4752 if (sip->query_data == NULL) {
4753 purple_connection_error(gc, _("Could not resolve hostname"));
4755 } else {
4756 /* TCP case */
4757 purple_debug_info("sipe", "using TCP\n");
4758 /* create socket for incoming connections */
4759 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4760 sipe_tcp_connect_listen_cb, sip);
4761 if (sip->listen_data == NULL) {
4762 purple_connection_error(gc, _("Could not create listen socket"));
4763 return;
4768 /* Service list for autodection */
4769 static const struct sipe_service_data service_autodetect[] = {
4770 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4771 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4772 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4773 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4774 { NULL, NULL, 0 }
4777 /* Service list for SSL/TLS */
4778 static const struct sipe_service_data service_tls[] = {
4779 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4780 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4781 { NULL, NULL, 0 }
4784 /* Service list for TCP */
4785 static const struct sipe_service_data service_tcp[] = {
4786 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4787 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4788 { NULL, NULL, 0 }
4791 /* Service list for UDP */
4792 static const struct sipe_service_data service_udp[] = {
4793 { "sip", "udp", SIPE_TRANSPORT_UDP },
4794 { NULL, NULL, 0 }
4797 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4798 static void resolve_next_service(struct sipe_account_data *sip,
4799 const struct sipe_service_data *start)
4801 if (start) {
4802 sip->service_data = start;
4803 } else {
4804 sip->service_data++;
4805 if (sip->service_data->service == NULL) {
4806 gchar *hostname;
4807 /* Try connecting to the SIP hostname directly */
4808 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4809 if (sip->auto_transport) {
4810 // If SSL is supported, default to using it; OCS servers aren't configured
4811 // by default to accept TCP
4812 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4813 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4814 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4817 hostname = g_strdup(sip->sipdomain);
4818 create_connection(sip, hostname, 0);
4819 return;
4823 /* Try to resolve next service */
4824 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4825 sip->service_data->transport,
4826 sip->sipdomain,
4827 srvresolved, sip);
4830 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4832 struct sipe_account_data *sip = data;
4834 sip->srv_query_data = NULL;
4836 /* find the host to connect to */
4837 if (results) {
4838 gchar *hostname = g_strdup(resp->hostname);
4839 int port = resp->port;
4840 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4841 hostname, port);
4842 g_free(resp);
4844 sip->transport = sip->service_data->type;
4846 create_connection(sip, hostname, port);
4847 } else {
4848 resolve_next_service(sip, NULL);
4852 static void sipe_login(PurpleAccount *account)
4854 PurpleConnection *gc;
4855 struct sipe_account_data *sip;
4856 gchar **signinname_login, **userserver, **domain_user;
4857 const char *transport;
4859 const char *username = purple_account_get_username(account);
4860 gc = purple_account_get_connection(account);
4862 if (strpbrk(username, "\t\v\r\n") != NULL) {
4863 gc->wants_to_die = TRUE;
4864 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
4865 return;
4868 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4869 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4870 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4871 sip->gc = gc;
4872 sip->account = account;
4873 sip->reregister_set = FALSE;
4874 sip->reauthenticate_set = FALSE;
4875 sip->subscribed = FALSE;
4876 sip->subscribed_buddies = FALSE;
4878 signinname_login = g_strsplit(username, ",", 2);
4880 userserver = g_strsplit(signinname_login[0], "@", 2);
4881 purple_connection_set_display_name(gc, userserver[0]);
4882 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4883 sip->sipdomain = g_strdup(userserver[1]);
4885 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
4886 gc->wants_to_die = TRUE;
4887 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4888 return;
4891 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4892 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4893 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4895 sip->password = g_strdup(purple_connection_get_password(gc));
4897 g_strfreev(userserver);
4898 g_strfreev(domain_user);
4899 g_strfreev(signinname_login);
4901 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4903 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4905 /* TODO: Set the status correctly. */
4906 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4908 transport = purple_account_get_string(account, "transport", "auto");
4909 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4910 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4911 SIPE_TRANSPORT_UDP;
4913 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4914 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4915 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4916 } else if (strcmp(transport, "auto") == 0) {
4917 sip->auto_transport = TRUE;
4918 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4919 } else if (strcmp(transport, "tls") == 0) {
4920 resolve_next_service(sip, service_tls);
4921 } else if (strcmp(transport, "tcp") == 0) {
4922 resolve_next_service(sip, service_tcp);
4923 } else {
4924 resolve_next_service(sip, service_udp);
4928 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4930 connection_free_all(sip);
4932 g_free(sip->epid);
4933 sip->epid = NULL;
4935 if (sip->query_data != NULL)
4936 purple_dnsquery_destroy(sip->query_data);
4937 sip->query_data = NULL;
4939 if (sip->srv_query_data != NULL)
4940 purple_srv_cancel(sip->srv_query_data);
4941 sip->srv_query_data = NULL;
4943 if (sip->listen_data != NULL)
4944 purple_network_listen_cancel(sip->listen_data);
4945 sip->listen_data = NULL;
4947 if (sip->gsc != NULL)
4948 purple_ssl_close(sip->gsc);
4949 sip->gsc = NULL;
4951 sipe_auth_free(&sip->registrar);
4952 sipe_auth_free(&sip->proxy);
4954 if (sip->txbuf)
4955 purple_circ_buffer_destroy(sip->txbuf);
4956 sip->txbuf = NULL;
4958 g_free(sip->realhostname);
4959 sip->realhostname = NULL;
4961 if (sip->listenpa)
4962 purple_input_remove(sip->listenpa);
4963 sip->listenpa = 0;
4964 if (sip->tx_handler)
4965 purple_input_remove(sip->tx_handler);
4966 sip->tx_handler = 0;
4967 if (sip->resendtimeout)
4968 purple_timeout_remove(sip->resendtimeout);
4969 sip->resendtimeout = 0;
4970 if (sip->timeouts) {
4971 GSList *entry = sip->timeouts;
4972 while (entry) {
4973 struct scheduled_action *sched_action = entry->data;
4974 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4975 purple_timeout_remove(sched_action->timeout_handler);
4976 g_free(sched_action->payload);
4977 g_free(sched_action->name);
4978 g_free(sched_action);
4979 entry = entry->next;
4982 g_slist_free(sip->timeouts);
4984 if (sip->allow_events) {
4985 GSList *entry = sip->allow_events;
4986 while (entry) {
4987 g_free(entry->data);
4988 entry = entry->next;
4991 g_slist_free(sip->allow_events);
4993 if (sip->contact)
4994 g_free(sip->contact);
4995 sip->contact = NULL;
4996 if (sip->regcallid)
4997 g_free(sip->regcallid);
4998 sip->regcallid = NULL;
5000 sip->fd = -1;
5001 sip->processing_input = FALSE;
5005 * A callback for g_hash_table_foreach_remove
5007 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
5009 sipe_free_buddy(buddy);
5012 static void sipe_close(PurpleConnection *gc)
5014 struct sipe_account_data *sip = gc->proto_data;
5016 if (sip) {
5017 /* leave all conversations */
5018 im_session_close_all(sip);
5020 /* unregister */
5021 do_register_exp(sip, 0);
5023 sipe_connection_cleanup(sip);
5024 g_free(sip->sipdomain);
5025 g_free(sip->username);
5026 g_free(sip->password);
5027 g_free(sip->authdomain);
5028 g_free(sip->authuser);
5029 g_free(sip->status);
5031 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
5032 g_hash_table_destroy(sip->buddies);
5034 g_free(gc->proto_data);
5035 gc->proto_data = NULL;
5038 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
5040 PurpleAccount *acct = purple_connection_get_account(gc);
5041 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
5042 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5043 if (conv == NULL)
5044 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5045 purple_conversation_present(conv);
5046 g_free(id);
5049 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5052 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5053 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
5056 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5058 PurpleNotifySearchResults *results;
5059 PurpleNotifySearchColumn *column;
5060 xmlnode *searchResults;
5061 xmlnode *mrow;
5062 int match_count = 0;
5063 gboolean more = FALSE;
5064 gchar *secondary;
5066 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5068 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5069 if (!searchResults) {
5070 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5071 return FALSE;
5074 results = purple_notify_searchresults_new();
5076 if (results == NULL) {
5077 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5078 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5080 xmlnode_free(searchResults);
5081 return FALSE;
5084 column = purple_notify_searchresults_column_new(_("User Name"));
5085 purple_notify_searchresults_column_add(results, column);
5087 column = purple_notify_searchresults_column_new(_("Name"));
5088 purple_notify_searchresults_column_add(results, column);
5090 column = purple_notify_searchresults_column_new(_("Company"));
5091 purple_notify_searchresults_column_add(results, column);
5093 column = purple_notify_searchresults_column_new(_("Country"));
5094 purple_notify_searchresults_column_add(results, column);
5096 column = purple_notify_searchresults_column_new(_("Email"));
5097 purple_notify_searchresults_column_add(results, column);
5099 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5100 GList *row = NULL;
5102 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5103 row = g_list_append(row, g_strdup(uri_parts[1]));
5104 g_strfreev(uri_parts);
5106 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5107 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5108 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5109 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5111 purple_notify_searchresults_row_add(results, row);
5112 match_count++;
5115 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5116 char *data = xmlnode_get_data_unescaped(mrow);
5117 more = (g_strcasecmp(data, "true") == 0);
5118 g_free(data);
5121 secondary = g_strdup_printf(
5122 dngettext(GETTEXT_PACKAGE,
5123 "Found %d contact%s:",
5124 "Found %d contacts%s:", match_count),
5125 match_count, more ? _(" (more matched your query)") : "");
5127 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5128 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5129 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5131 g_free(secondary);
5132 xmlnode_free(searchResults);
5133 return TRUE;
5136 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5138 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5139 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5140 unsigned i = 0;
5142 do {
5143 PurpleRequestField *field = entries->data;
5144 const char *id = purple_request_field_get_id(field);
5145 const char *value = purple_request_field_string_get_value(field);
5147 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5149 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5150 } while ((entries = g_list_next(entries)) != NULL);
5151 attrs[i] = NULL;
5153 if (i > 0) {
5154 gchar *query = g_strjoinv(NULL, attrs);
5155 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5156 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5157 send_soap_request_with_cb(gc->proto_data, body,
5158 (TransCallback) process_search_contact_response, NULL);
5159 g_free(body);
5160 g_free(query);
5163 g_strfreev(attrs);
5166 static void sipe_show_find_contact(PurplePluginAction *action)
5168 PurpleConnection *gc = (PurpleConnection *) action->context;
5169 PurpleRequestFields *fields;
5170 PurpleRequestFieldGroup *group;
5171 PurpleRequestField *field;
5173 fields = purple_request_fields_new();
5174 group = purple_request_field_group_new(NULL);
5175 purple_request_fields_add_group(fields, group);
5177 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5178 purple_request_field_group_add_field(group, field);
5179 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5180 purple_request_field_group_add_field(group, field);
5181 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5182 purple_request_field_group_add_field(group, field);
5183 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5184 purple_request_field_group_add_field(group, field);
5186 purple_request_fields(gc,
5187 _("Search"),
5188 _("Search for a Contact"),
5189 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5190 fields,
5191 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5192 _("_Cancel"), NULL,
5193 purple_connection_get_account(gc), NULL, NULL, gc);
5196 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5198 GList *menu = NULL;
5199 PurplePluginAction *act;
5201 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5202 menu = g_list_prepend(menu, act);
5204 menu = g_list_reverse(menu);
5206 return menu;
5209 static void dummy_permit_deny(PurpleConnection *gc)
5213 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5215 return TRUE;
5219 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5221 return TRUE;
5225 static char *sipe_status_text(PurpleBuddy *buddy)
5227 struct sipe_account_data *sip;
5228 struct sipe_buddy *sbuddy;
5229 char *text = NULL;
5231 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5232 if (sip) //happens on pidgin exit
5234 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5235 if (sbuddy && sbuddy->annotation)
5237 text = g_strdup(sbuddy->annotation);
5241 return text;
5244 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5246 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5247 const PurpleStatus *status = purple_presence_get_active_status(presence);
5248 struct sipe_account_data *sip;
5249 struct sipe_buddy *sbuddy;
5250 char *annotation = NULL;
5252 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5253 if (sip) //happens on pidgin exit
5255 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5256 if (sbuddy)
5258 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5262 //Layout
5263 if (purple_presence_is_online(presence))
5265 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5268 if (annotation)
5270 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5271 g_free(annotation);
5276 static GHashTable *
5277 sipe_get_account_text_table(PurpleAccount *account)
5279 GHashTable *table;
5280 table = g_hash_table_new(g_str_hash, g_str_equal);
5281 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5282 return table;
5285 static PurpleBuddy *
5286 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5288 PurpleBuddy *clone;
5289 const gchar *server_alias, *email;
5290 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5292 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5294 purple_blist_add_buddy(clone, NULL, group, NULL);
5296 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5297 if (server_alias) {
5298 purple_blist_server_alias_buddy(clone, server_alias);
5301 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5302 if (email) {
5303 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5306 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5307 //for UI to update;
5308 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5309 return clone;
5312 static void
5313 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5315 PurpleBuddy *buddy, *b;
5316 PurpleConnection *gc;
5317 PurpleGroup * group = purple_find_group(group_name);
5319 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5321 buddy = (PurpleBuddy *)node;
5323 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5324 gc = purple_account_get_connection(buddy->account);
5326 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5327 if (!b){
5328 b = purple_blist_add_buddy_clone(group, buddy);
5331 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5334 static void
5335 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5337 const gchar *email;
5338 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5340 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5341 if (email)
5343 char *mailto = g_strdup_printf("mailto:%s", email);
5344 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5345 #ifndef _WIN32
5347 pid_t pid;
5348 char *const parmList[] = {mailto, NULL};
5349 if ((pid = fork()) == -1)
5351 purple_debug_info("sipe", "fork() error\n");
5353 else if (pid == 0)
5355 execvp("xdg-email", parmList);
5356 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5359 #else
5361 BOOL ret;
5362 _flushall();
5363 errno = 0;
5364 //@TODO resolve env variable %WINDIR% first
5365 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5366 if (errno)
5368 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5371 #endif
5373 g_free(mailto);
5375 else
5377 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5382 * A menu which appear when right-clicking on buddy in contact list.
5384 static GList *
5385 sipe_buddy_menu(PurpleBuddy *buddy)
5387 PurpleBlistNode *g_node;
5388 PurpleGroup *group, *gr_parent;
5389 PurpleMenuAction *act;
5390 GList *menu = NULL;
5391 GList *menu_groups = NULL;
5393 act = purple_menu_action_new(_("Send Email..."),
5394 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5395 NULL, NULL);
5396 menu = g_list_prepend(menu, act);
5398 gr_parent = purple_buddy_get_group(buddy);
5399 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5400 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5401 continue;
5403 group = (PurpleGroup *)g_node;
5404 if (group == gr_parent)
5405 continue;
5407 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5408 continue;
5410 act = purple_menu_action_new(purple_group_get_name(group),
5411 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5412 group->name, NULL);
5413 menu_groups = g_list_prepend(menu_groups, act);
5415 menu_groups = g_list_reverse(menu_groups);
5417 act = purple_menu_action_new(_("Copy to"),
5418 NULL,
5419 NULL, menu_groups);
5420 menu = g_list_prepend(menu, act);
5421 menu = g_list_reverse(menu);
5423 return menu;
5426 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5427 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5428 return sipe_buddy_menu((PurpleBuddy *) node);
5429 } else {
5430 return NULL;
5434 static gboolean
5435 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5437 gboolean ret = TRUE;
5438 char *username = (char *)trans->payload;
5440 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5441 PurpleBuddy *pbuddy;
5442 struct sipe_buddy *sbuddy;
5443 const char *alias;
5444 char *server_alias = NULL;
5445 char *email = NULL;
5446 const char *device_name = NULL;
5448 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5450 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5451 alias = purple_buddy_get_local_alias(pbuddy);
5453 if (sip)
5455 //will query buddy UA's capabilities and send answer to log
5456 sipe_options_request(sip, username);
5458 sbuddy = g_hash_table_lookup(sip->buddies, username);
5459 if (sbuddy)
5461 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5465 if (msg->response != 200) {
5466 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5467 } else {
5468 xmlnode *searchResults;
5469 xmlnode *mrow;
5471 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5472 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5473 if (!searchResults) {
5474 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5475 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5476 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5477 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5478 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5479 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5480 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5481 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5482 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5483 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5484 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5485 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5486 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5487 if (!email || strcmp("", email)) {
5488 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5489 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5493 xmlnode_free(searchResults);
5496 purple_notify_user_info_add_section_break(info);
5498 if (!server_alias || !strcmp("", server_alias)) {
5499 g_free(server_alias);
5500 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5501 if (server_alias) {
5502 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5506 // same as server alias, do not present
5507 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5508 if (alias)
5510 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5513 if (!email || !strcmp("", email)) {
5514 g_free(email);
5515 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5516 if (email) {
5517 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5521 if (device_name)
5523 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5526 /* show a buddy's user info in a nice dialog box */
5527 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5528 username, /* buddy's username */
5529 info, /* body */
5530 NULL, /* callback called when dialog closed */
5531 NULL); /* userdata for callback */
5533 return ret;
5537 * AD search first, LDAP based
5539 static void sipe_get_info(PurpleConnection *gc, const char *username)
5541 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5542 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5544 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5545 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5546 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5547 g_free(body);
5548 g_free(row);
5551 static PurplePlugin *my_protocol = NULL;
5553 static PurplePluginProtocolInfo prpl_info =
5556 NULL, /* user_splits */
5557 NULL, /* protocol_options */
5558 NO_BUDDY_ICONS, /* icon_spec */
5559 sipe_list_icon, /* list_icon */
5560 NULL, /* list_emblems */
5561 sipe_status_text, /* status_text */
5562 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5563 sipe_status_types, /* away_states */
5564 sipe_blist_node_menu, /* blist_node_menu */
5565 NULL, /* chat_info */
5566 NULL, /* chat_info_defaults */
5567 sipe_login, /* login */
5568 sipe_close, /* close */
5569 sipe_im_send, /* send_im */
5570 NULL, /* set_info */ // TODO maybe
5571 sipe_send_typing, /* send_typing */
5572 sipe_get_info, /* get_info */
5573 sipe_set_status, /* set_status */
5574 NULL, /* set_idle */
5575 NULL, /* change_passwd */
5576 sipe_add_buddy, /* add_buddy */
5577 NULL, /* add_buddies */
5578 sipe_remove_buddy, /* remove_buddy */
5579 NULL, /* remove_buddies */
5580 sipe_add_permit, /* add_permit */
5581 sipe_add_deny, /* add_deny */
5582 sipe_add_deny, /* rem_permit */
5583 sipe_add_permit, /* rem_deny */
5584 dummy_permit_deny, /* set_permit_deny */
5585 NULL, /* join_chat */
5586 NULL, /* reject_chat */
5587 NULL, /* get_chat_name */
5588 NULL, /* chat_invite */
5589 NULL, /* chat_leave */
5590 NULL, /* chat_whisper */
5591 NULL, /* chat_send */
5592 sipe_keep_alive, /* keepalive */
5593 NULL, /* register_user */
5594 NULL, /* get_cb_info */ // deprecated
5595 NULL, /* get_cb_away */ // deprecated
5596 sipe_alias_buddy, /* alias_buddy */
5597 sipe_group_buddy, /* group_buddy */
5598 sipe_rename_group, /* rename_group */
5599 NULL, /* buddy_free */
5600 sipe_convo_closed, /* convo_closed */
5601 purple_normalize_nocase, /* normalize */
5602 NULL, /* set_buddy_icon */
5603 sipe_remove_group, /* remove_group */
5604 NULL, /* get_cb_real_name */ // TODO?
5605 NULL, /* set_chat_topic */
5606 NULL, /* find_blist_chat */
5607 NULL, /* roomlist_get_list */
5608 NULL, /* roomlist_cancel */
5609 NULL, /* roomlist_expand_category */
5610 NULL, /* can_receive_file */
5611 NULL, /* send_file */
5612 NULL, /* new_xfer */
5613 NULL, /* offline_message */
5614 NULL, /* whiteboard_prpl_ops */
5615 sipe_send_raw, /* send_raw */
5616 NULL, /* roomlist_room_serialize */
5617 NULL, /* unregister_user */
5618 NULL, /* send_attention */
5619 NULL, /* get_attention_types */
5621 sizeof(PurplePluginProtocolInfo), /* struct_size */
5622 sipe_get_account_text_table, /* get_account_text_table */
5626 static PurplePluginInfo info = {
5627 PURPLE_PLUGIN_MAGIC,
5628 PURPLE_MAJOR_VERSION,
5629 PURPLE_MINOR_VERSION,
5630 PURPLE_PLUGIN_PROTOCOL, /**< type */
5631 NULL, /**< ui_requirement */
5632 0, /**< flags */
5633 NULL, /**< dependencies */
5634 PURPLE_PRIORITY_DEFAULT, /**< priority */
5635 "prpl-sipe", /**< id */
5636 "Microsoft LCS/OCS", /**< name */
5637 VERSION, /**< version */
5638 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5639 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5640 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5641 "Gabriel Burt <gburt@novell.com>", /**< author */
5642 PURPLE_WEBSITE, /**< homepage */
5643 sipe_plugin_load, /**< load */
5644 sipe_plugin_unload, /**< unload */
5645 sipe_plugin_destroy, /**< destroy */
5646 NULL, /**< ui_info */
5647 &prpl_info, /**< extra_info */
5648 NULL,
5649 sipe_actions,
5650 NULL,
5651 NULL,
5652 NULL,
5653 NULL
5656 static void sipe_plugin_destroy(PurplePlugin *plugin)
5658 GList *entry;
5660 entry = prpl_info.protocol_options;
5661 while (entry) {
5662 purple_account_option_destroy(entry->data);
5663 entry = g_list_delete_link(entry, entry);
5665 prpl_info.protocol_options = NULL;
5667 entry = prpl_info.user_splits;
5668 while (entry) {
5669 purple_account_user_split_destroy(entry->data);
5670 entry = g_list_delete_link(entry, entry);
5672 prpl_info.user_splits = NULL;
5675 static void init_plugin(PurplePlugin *plugin)
5677 PurpleAccountUserSplit *split;
5678 PurpleAccountOption *option;
5680 #ifdef ENABLE_NLS
5681 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5682 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5683 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5684 #endif
5686 purple_plugin_register(plugin);
5688 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5689 purple_account_user_split_set_reverse(split, FALSE);
5690 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5692 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5693 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5694 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5695 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5697 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5698 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5699 // Translators: noun (networking port)
5700 option = purple_account_option_int_new(_("Port"), "port", 5061);
5701 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5703 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5704 purple_account_option_add_list_item(option, _("Auto"), "auto");
5705 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5706 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5707 purple_account_option_add_list_item(option, _("UDP"), "udp");
5708 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5710 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5711 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5713 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5714 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5716 // TODO commented out so won't show in the preferences until we fix krb message signing
5717 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5718 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5720 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5721 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5722 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5725 my_protocol = plugin;
5728 /* I had to redefined the function for it load, but works */
5729 gboolean purple_init_plugin(PurplePlugin *plugin){
5730 plugin->info = &(info);
5731 init_plugin((plugin));
5732 sipe_plugin_load((plugin));
5733 return purple_plugin_register(plugin);
5737 Local Variables:
5738 mode: c
5739 c-file-style: "bsd"
5740 indent-tabs-mode: t
5741 tab-width: 8
5742 End: