fix for crash in process_incoming_notify_rlmi_resub/
[siplcs.git] / src / sipe.c
blob549cc9834bb3ba98cec8c5b96a37b42e3b5ecf00
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 static char *gentag()
111 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
114 static gchar *get_epid(struct sipe_account_data *sip)
116 if (!sip->epid) {
117 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
119 return g_strdup(sip->epid);
122 static char *genbranch()
124 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
125 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
126 rand() & 0xFFFF, rand() & 0xFFFF);
129 static char *gencallid()
131 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
132 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
133 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
134 rand() & 0xFFFF, rand() & 0xFFFF);
137 static gchar *find_tag(const gchar *hdr)
139 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
140 if (!tag) {
141 // In case it's at the end and there's no trailing ;
142 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
144 return tag;
148 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
150 return "sipe";
153 static void sipe_plugin_destroy(PurplePlugin *plugin);
155 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
157 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
158 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
159 gpointer data);
161 static void sipe_close(PurpleConnection *gc);
163 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name);
164 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip);
165 static void send_presence_status(struct sipe_account_data *sip);
167 static void sendout_pkt(PurpleConnection *gc, const char *buf);
169 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, const gchar *hdr)
171 gchar *timeout = sipmsg_find_part_of_header(hdr, "timeout=", ";", NULL);
172 if (timeout != NULL) {
173 sscanf(timeout, "%u", &sip->keepalive_timeout);
174 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
175 sip->keepalive_timeout);
177 g_free(timeout);
180 static void sipe_keep_alive(PurpleConnection *gc)
182 struct sipe_account_data *sip = gc->proto_data;
183 if (sip->transport == SIPE_TRANSPORT_UDP) {
184 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
185 gchar buf[2] = {0, 0};
186 purple_debug_info("sipe", "sending keep alive\n");
187 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
188 } else {
189 time_t now = time(NULL);
190 if ((sip->keepalive_timeout > 0) &&
191 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
192 #if PURPLE_VERSION_CHECK(2,4,0)
193 && ((now - gc->last_received) >= sip->keepalive_timeout)
194 #endif
196 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
197 sendout_pkt(gc, "\r\n\r\n");
198 sip->last_keepalive = now;
203 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
205 struct sip_connection *ret = NULL;
206 GSList *entry = sip->openconns;
207 while (entry) {
208 ret = entry->data;
209 if (ret->fd == fd) return ret;
210 entry = entry->next;
212 return NULL;
215 static void sipe_auth_free(struct sip_auth *auth)
217 g_free(auth->nonce);
218 auth->nonce = NULL;
219 g_free(auth->opaque);
220 auth->opaque = NULL;
221 g_free(auth->realm);
222 auth->realm = NULL;
223 g_free(auth->target);
224 auth->target = NULL;
225 g_free(auth->digest_session_key);
226 auth->digest_session_key = NULL;
227 g_free(auth->ntlm_key);
228 auth->ntlm_key = NULL;
229 auth->type = AUTH_TYPE_UNSET;
230 auth->retries = 0;
231 auth->expires = 0;
234 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
236 struct sip_connection *ret = g_new0(struct sip_connection, 1);
237 ret->fd = fd;
238 sip->openconns = g_slist_append(sip->openconns, ret);
239 return ret;
242 static void connection_remove(struct sipe_account_data *sip, int fd)
244 struct sip_connection *conn = connection_find(sip, fd);
245 if (conn) {
246 sip->openconns = g_slist_remove(sip->openconns, conn);
247 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
248 g_free(conn->inbuf);
249 g_free(conn);
253 static void connection_free_all(struct sipe_account_data *sip)
255 struct sip_connection *ret = NULL;
256 GSList *entry = sip->openconns;
257 while (entry) {
258 ret = entry->data;
259 connection_remove(sip, ret->fd);
260 entry = sip->openconns;
264 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
266 const gchar *method = msg->method;
267 const gchar *target = msg->target;
268 gchar noncecount[9];
269 gchar *response;
270 gchar *ret;
271 gchar *tmp = NULL;
272 const char *authdomain = sip->authdomain;
273 const char *authuser = sip->authuser;
274 //const char *krb5_realm;
275 const char *host;
276 //gchar *krb5_token = NULL;
278 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
279 // and do error checking
281 // KRB realm should always be uppercase
282 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
284 if (sip->realhostname) {
285 host = sip->realhostname;
286 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
287 host = purple_account_get_string(sip->account, "proxy", "");
288 } else {
289 host = sip->sipdomain;
292 /*gboolean new_auth = krb5_auth.gss_context == NULL;
293 if (new_auth) {
294 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
297 if (new_auth || force_reauth) {
298 krb5_token = krb5_auth.base64_token;
301 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
302 krb5_token = krb5_auth.base64_token;*/
304 if (!authdomain) {
305 authdomain = "";
308 if (!authuser || strlen(authuser) < 1) {
309 authuser = sip->username;
312 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
313 sprintf(noncecount, "%08d", auth->nc++);
314 response = purple_cipher_http_digest_calculate_response(
315 "md5", method, target, NULL, NULL,
316 auth->nonce, noncecount, NULL, auth->digest_session_key);
317 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
319 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);
320 g_free(response);
321 return ret;
322 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
323 // If we have a signature for the message, include that
324 if (msg->signature) {
325 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);
326 return tmp;
329 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
330 const gchar *ntlm_key;
331 gchar *gssapi_data;
332 #if GLIB_CHECK_VERSION(2,8,0)
333 const gchar * hostname = g_get_host_name();
334 #else
335 static char hostname[256];
336 int ret = gethostname(hostname, sizeof(hostname));
337 hostname[sizeof(hostname) - 1] = '\0';
338 if (ret == -1 || hostname[0] == '\0') {
339 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
340 g_strerror(errno);
341 strcpy(hostname, "localhost");
343 #endif
344 /*const gchar * hostname = purple_get_host_name();*/
346 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
347 auth->ntlm_key = (gchar *)ntlm_key;
348 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
349 g_free(gssapi_data);
350 return tmp;
353 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
354 return tmp;
355 } else if (auth->type == AUTH_TYPE_KERBEROS) {
356 /* Kerberos */
357 if (auth->nc == 3) {
358 /*if (new_auth || force_reauth) {
359 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
360 if (auth->opaque) {
361 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);
362 } else {
363 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
365 } else {
366 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
367 gchar * mic = "MICTODO";
368 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
369 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
370 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
371 //auth->opaque ? auth->opaque : "", auth->target);
372 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
373 //g_free(mic);
375 return tmp;
377 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
380 sprintf(noncecount, "%08d", auth->nc++);
381 response = purple_cipher_http_digest_calculate_response(
382 "md5", method, target, NULL, NULL,
383 auth->nonce, noncecount, NULL, auth->digest_session_key);
384 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
386 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);
387 g_free(response);
388 return ret;
391 static char *parse_attribute(const char *attrname, const char *source)
393 const char *tmp, *tmp2;
394 char *retval = NULL;
395 int len = strlen(attrname);
397 if (!strncmp(source, attrname, len)) {
398 tmp = source + len;
399 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
400 if (tmp2)
401 retval = g_strndup(tmp, tmp2 - tmp);
402 else
403 retval = g_strdup(tmp);
406 return retval;
409 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
411 int i = 0;
412 const char *authuser;
413 char *tmp;
414 gchar **parts;
415 //const char *krb5_realm;
416 //const char *host;
418 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
419 // and do error checking
421 // KRB realm should always be uppercase
422 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
424 if (sip->realhostname) {
425 host = sip->realhostname;
426 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
427 host = purple_account_get_string(sip->account, "proxy", "");
428 } else {
429 host = sip->sipdomain;
432 authuser = sip->authuser;
434 if (!authuser || strlen(authuser) < 1) {
435 authuser = sip->username;
438 if (!hdr) {
439 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
440 return;
443 if (!g_strncasecmp(hdr, "NTLM", 4)) {
444 auth->type = AUTH_TYPE_NTLM;
445 parts = g_strsplit(hdr+5, "\", ", 0);
446 i = 0;
447 while (parts[i]) {
448 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
449 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
450 g_free(auth->nonce);
451 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
452 g_free(tmp);
454 if ((tmp = parse_attribute("targetname=\"",
455 parts[i]))) {
456 g_free(auth->target);
457 auth->target = tmp;
459 else if ((tmp = parse_attribute("realm=\"",
460 parts[i]))) {
461 g_free(auth->realm);
462 auth->realm = tmp;
464 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
465 g_free(auth->opaque);
466 auth->opaque = tmp;
468 i++;
470 g_strfreev(parts);
471 auth->nc = 1;
472 if (!strstr(hdr, "gssapi-data")) {
473 auth->nc = 1;
474 } else {
475 auth->nc = 3;
477 return;
480 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
481 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
482 auth->type = AUTH_TYPE_KERBEROS;
483 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
484 parts = g_strsplit(hdr+9, "\", ", 0);
485 i = 0;
486 while (parts[i]) {
487 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
488 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
489 /*if (krb5_auth.gss_context == NULL) {
490 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
492 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
493 g_free(tmp);
495 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
496 g_free(auth->target);
497 auth->target = tmp;
498 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
499 g_free(auth->realm);
500 auth->realm = tmp;
501 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
502 g_free(auth->opaque);
503 auth->opaque = tmp;
505 i++;
507 g_strfreev(parts);
508 auth->nc = 3;
509 return;
512 auth->type = AUTH_TYPE_DIGEST;
513 parts = g_strsplit(hdr, " ", 0);
514 while (parts[i]) {
515 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
516 g_free(auth->nonce);
517 auth->nonce = tmp;
519 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
520 g_free(auth->realm);
521 auth->realm = tmp;
523 i++;
525 g_strfreev(parts);
527 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
528 if (auth->realm) {
529 g_free(auth->digest_session_key);
530 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
531 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
533 auth->nc = 1;
537 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
539 PurpleConnection *gc = data;
540 struct sipe_account_data *sip = gc->proto_data;
541 gsize max_write;
542 gssize written;
544 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
546 if (max_write == 0) {
547 if (sip->tx_handler != 0){
548 purple_input_remove(sip->tx_handler);
549 sip->tx_handler = 0;
551 return;
554 written = write(sip->fd, sip->txbuf->outptr, max_write);
556 if (written < 0 && errno == EAGAIN)
557 written = 0;
558 else if (written <= 0) {
559 /*TODO: do we really want to disconnect on a failure to write?*/
560 purple_connection_error(gc, _("Could not write"));
561 return;
564 purple_circ_buffer_mark_read(sip->txbuf, written);
567 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
569 PurpleConnection *gc = data;
570 struct sipe_account_data *sip = gc->proto_data;
571 gsize max_write;
572 gssize written;
574 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
576 if (max_write == 0) {
577 if (sip->tx_handler != 0) {
578 purple_input_remove(sip->tx_handler);
579 sip->tx_handler = 0;
580 return;
584 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
586 if (written < 0 && errno == EAGAIN)
587 written = 0;
588 else if (written <= 0) {
589 /*TODO: do we really want to disconnect on a failure to write?*/
590 purple_connection_error(gc, _("Could not write"));
591 return;
594 purple_circ_buffer_mark_read(sip->txbuf, written);
597 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
599 static void send_later_cb(gpointer data, gint source, const gchar *error)
601 PurpleConnection *gc = data;
602 struct sipe_account_data *sip;
603 struct sip_connection *conn;
605 if (!PURPLE_CONNECTION_IS_VALID(gc))
607 if (source >= 0)
608 close(source);
609 return;
612 if (source < 0) {
613 purple_connection_error(gc, _("Could not connect"));
614 return;
617 sip = gc->proto_data;
618 sip->fd = source;
619 sip->connecting = FALSE;
620 sip->last_keepalive = time(NULL);
622 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
624 /* If there is more to write now, we need to register a handler */
625 if (sip->txbuf->bufused > 0)
626 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
628 conn = connection_create(sip, source);
629 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
632 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
634 struct sipe_account_data *sip;
635 struct sip_connection *conn;
637 if (!PURPLE_CONNECTION_IS_VALID(gc))
639 if (gsc) purple_ssl_close(gsc);
640 return NULL;
643 sip = gc->proto_data;
644 sip->fd = gsc->fd;
645 sip->gsc = gsc;
646 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
647 sip->connecting = FALSE;
648 sip->last_keepalive = time(NULL);
650 conn = connection_create(sip, gsc->fd);
652 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
654 return sip;
657 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
659 PurpleConnection *gc = data;
660 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
661 if (sip == NULL) return;
663 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
665 /* If there is more to write now */
666 if (sip->txbuf->bufused > 0) {
667 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
672 static void sendlater(PurpleConnection *gc, const char *buf)
674 struct sipe_account_data *sip = gc->proto_data;
676 if (!sip->connecting) {
677 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
678 if (sip->transport == SIPE_TRANSPORT_TLS){
679 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
680 } else {
681 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
682 purple_connection_error(gc, _("Couldn't create socket"));
685 sip->connecting = TRUE;
688 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
689 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
691 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
694 static void sendout_pkt(PurpleConnection *gc, const char *buf)
696 struct sipe_account_data *sip = gc->proto_data;
697 time_t currtime = time(NULL);
698 int writelen = strlen(buf);
700 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
701 if (sip->transport == SIPE_TRANSPORT_UDP) {
702 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
703 purple_debug_info("sipe", "could not send packet\n");
705 } else {
706 int ret;
707 if (sip->fd < 0) {
708 sendlater(gc, buf);
709 return;
712 if (sip->tx_handler) {
713 ret = -1;
714 errno = EAGAIN;
715 } else{
716 if (sip->gsc){
717 ret = purple_ssl_write(sip->gsc, buf, writelen);
718 }else{
719 ret = write(sip->fd, buf, writelen);
723 if (ret < 0 && errno == EAGAIN)
724 ret = 0;
725 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
726 sendlater(gc, buf);
727 return;
730 if (ret < writelen) {
731 if (!sip->tx_handler){
732 if (sip->gsc){
733 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
735 else{
736 sip->tx_handler = purple_input_add(sip->fd,
737 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
738 gc);
742 /* XXX: is it OK to do this? You might get part of a request sent
743 with part of another. */
744 if (sip->txbuf->bufused > 0)
745 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
747 purple_circ_buffer_append(sip->txbuf, buf + ret,
748 writelen - ret);
753 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
755 sendout_pkt(gc, buf);
756 return len;
759 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
761 GSList *tmp = msg->headers;
762 gchar *name;
763 gchar *value;
764 GString *outstr = g_string_new("");
765 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
766 while (tmp) {
767 name = ((struct siphdrelement*) (tmp->data))->name;
768 value = ((struct siphdrelement*) (tmp->data))->value;
769 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
770 tmp = g_slist_next(tmp);
772 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
773 sendout_pkt(sip->gc, outstr->str);
774 g_string_free(outstr, TRUE);
777 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
779 gchar * buf;
780 if (sip->registrar.ntlm_key) {
781 struct sipmsg_breakdown msgbd;
782 gchar *signature_input_str;
783 msgbd.msg = msg;
784 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
785 msgbd.rand = g_strdup_printf("%08x", g_random_int());
786 sip->registrar.ntlm_num++;
787 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
788 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
789 if (signature_input_str != NULL) {
790 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
791 msg->rand = g_strdup(msgbd.rand);
792 msg->num = g_strdup(msgbd.num);
793 g_free(signature_input_str);
795 sipmsg_breakdown_free(&msgbd);
798 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
799 buf = auth_header(sip, &sip->registrar, msg);
800 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
801 sipmsg_add_header(msg, "Authorization", buf);
802 } else {
803 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
805 g_free(buf);
806 } 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")) {
807 sip->registrar.nc = 3;
808 sip->registrar.type = AUTH_TYPE_NTLM;
810 buf = auth_header(sip, &sip->registrar, msg);
811 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
812 g_free(buf);
813 } else {
814 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
818 static char *get_contact(struct sipe_account_data *sip)
820 return g_strdup(sip->contact);
824 * unused. Needed?
825 static char *get_contact_service(struct sipe_account_data *sip)
827 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()));
828 //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);
832 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
833 const char *text, const char *body)
835 gchar *name;
836 gchar *value;
837 GString *outstr = g_string_new("");
838 struct sipe_account_data *sip = gc->proto_data;
839 gchar *contact;
840 GSList *tmp;
842 sipmsg_remove_header(msg, "ms-user-data");
844 contact = get_contact(sip);
845 sipmsg_remove_header(msg, "Contact");
846 sipmsg_add_header(msg, "Contact", contact);
847 g_free(contact);
849 /* When sending the acknowlegements and errors, the content length from the original
850 message is still here, but there is no body; we need to make sure we're sending the
851 correct content length */
852 sipmsg_remove_header(msg, "Content-Length");
853 if (body) {
854 gchar len[12];
855 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
856 sipmsg_add_header(msg, "Content-Length", len);
857 } else {
858 sipmsg_remove_header(msg, "Content-Type");
859 sipmsg_add_header(msg, "Content-Length", "0");
862 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
863 //gchar * mic = "MICTODO";
864 msg->response = code;
866 sipmsg_remove_header(msg, "Authentication-Info");
867 sign_outgoing_message(msg, sip, msg->method);
869 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
870 tmp = msg->headers;
871 while (tmp) {
872 name = ((struct siphdrelement*) (tmp->data))->name;
873 value = ((struct siphdrelement*) (tmp->data))->value;
875 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
876 tmp = g_slist_next(tmp);
878 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
879 sendout_pkt(gc, outstr->str);
880 g_string_free(outstr, TRUE);
883 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
885 if (trans->msg) sipmsg_free(trans->msg);
886 sip->transactions = g_slist_remove(sip->transactions, trans);
887 g_free(trans);
890 static struct transaction *
891 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
893 struct transaction *trans = g_new0(struct transaction, 1);
894 trans->time = time(NULL);
895 trans->msg = (struct sipmsg *)msg;
896 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
897 trans->callback = callback;
898 sip->transactions = g_slist_append(sip->transactions, trans);
899 return trans;
902 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
904 struct transaction *trans;
905 GSList *transactions = sip->transactions;
906 gchar *cseq = sipmsg_find_header(msg, "CSeq");
908 while (transactions) {
909 trans = transactions->data;
910 if (!strcmp(trans->cseq, cseq)) {
911 return trans;
913 transactions = transactions->next;
916 return NULL;
919 static struct transaction *
920 send_sip_request(PurpleConnection *gc, const gchar *method,
921 const gchar *url, const gchar *to, const gchar *addheaders,
922 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
924 struct sipe_account_data *sip = gc->proto_data;
925 const char *addh = "";
926 char *buf;
927 struct sipmsg *msg;
928 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
929 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
930 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
931 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
932 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
933 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
934 gchar *route = strdup("");
935 gchar *epid = get_epid(sip); // TODO generate one per account/login
936 struct transaction *trans;
938 if (dialog && dialog->routes)
940 GSList *iter = dialog->routes;
942 while(iter)
944 char *tmp = route;
945 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
946 g_free(tmp);
947 iter = g_slist_next(iter);
951 if (!ourtag && !dialog) {
952 ourtag = gentag();
955 if (!strcmp(method, "REGISTER")) {
956 if (sip->regcallid) {
957 g_free(callid);
958 callid = g_strdup(sip->regcallid);
959 } else {
960 sip->regcallid = g_strdup(callid);
964 if (addheaders) addh = addheaders;
966 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
967 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
968 "From: <sip:%s>%s%s;epid=%s\r\n"
969 "To: <%s>%s%s%s%s\r\n"
970 "Max-Forwards: 70\r\n"
971 "CSeq: %d %s\r\n"
972 "User-Agent: %s\r\n"
973 "Call-ID: %s\r\n"
974 "%s%s"
975 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
976 method,
977 dialog && dialog->request ? dialog->request : url,
978 TRANSPORT_DESCRIPTOR,
979 purple_network_get_my_ip(-1),
980 sip->listenport,
981 branch ? ";branch=" : "",
982 branch ? branch : "",
983 sip->username,
984 ourtag ? ";tag=" : "",
985 ourtag ? ourtag : "",
986 epid,
988 theirtag ? ";tag=" : "",
989 theirtag ? theirtag : "",
990 theirepid ? ";epid=" : "",
991 theirepid ? theirepid : "",
992 dialog ? ++dialog->cseq : ++sip->cseq,
993 method,
994 useragent,
995 callid,
996 route,
997 addh,
998 body ? strlen(body) : 0,
999 body ? body : "");
1002 //printf ("parsing msg buf:\n%s\n\n", buf);
1003 msg = sipmsg_parse_msg(buf);
1005 g_free(buf);
1006 g_free(ourtag);
1007 g_free(theirtag);
1008 g_free(theirepid);
1009 g_free(branch);
1010 g_free(callid);
1011 g_free(route);
1012 g_free(epid);
1014 sign_outgoing_message (msg, sip, method);
1016 buf = sipmsg_to_string (msg);
1018 /* add to ongoing transactions */
1019 trans = transactions_add_buf(sip, msg, tc);
1020 sendout_pkt(gc, buf);
1021 g_free(buf);
1023 return trans;
1026 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1028 gchar *from = g_strdup_printf("sip:%s", sip->username);
1029 gchar *contact = get_contact(sip);
1030 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1031 "Content-Type: application/SOAP+xml\r\n",contact);
1033 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1034 tr->payload = payload;
1036 g_free(from);
1037 g_free(contact);
1038 g_free(hdr);
1041 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1043 send_soap_request_with_cb(sip, body, NULL, NULL);
1046 static char *get_contact_register(struct sipe_account_data *sip)
1048 char *epid = get_epid(sip);
1049 char *uuid = generateUUIDfromEPID(epid);
1050 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);
1051 g_free(uuid);
1052 g_free(epid);
1053 return(buf);
1056 static void do_register_exp(struct sipe_account_data *sip, int expire)
1058 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1059 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1060 char *to = g_strdup_printf("sip:%s", sip->username);
1061 char *contact = get_contact_register(sip);
1062 char *hdr = g_strdup_printf("Contact: %s\r\n"
1063 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1064 "Event: registration\r\n"
1065 "Allow-Events: presence\r\n"
1066 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1067 "%s", contact, expires);
1068 g_free(contact);
1069 g_free(expires);
1071 sip->registerstatus = 1;
1073 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1074 process_register_response);
1076 g_free(hdr);
1077 g_free(uri);
1078 g_free(to);
1081 static void do_register_cb(struct sipe_account_data *sip)
1083 do_register_exp(sip, -1);
1084 sip->reregister_set = FALSE;
1087 static void do_register(struct sipe_account_data *sip)
1089 do_register_exp(sip, -1);
1093 * Returns URI from provided To or From header.
1095 * Needs to g_free() after use.
1097 * @return URI with sip: prefix
1099 static gchar *parse_from(const gchar *hdr)
1101 gchar *from;
1102 const gchar *tmp, *tmp2 = hdr;
1104 if (!hdr) return NULL;
1105 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1106 tmp = strchr(hdr, '<');
1108 /* i hate the different SIP UA behaviours... */
1109 if (tmp) { /* sip address in <...> */
1110 tmp2 = tmp + 1;
1111 tmp = strchr(tmp2, '>');
1112 if (tmp) {
1113 from = g_strndup(tmp2, tmp - tmp2);
1114 } else {
1115 purple_debug_info("sipe", "found < without > in From\n");
1116 return NULL;
1118 } else {
1119 tmp = strchr(tmp2, ';');
1120 if (tmp) {
1121 from = g_strndup(tmp2, tmp - tmp2);
1122 } else {
1123 from = g_strdup(tmp2);
1126 purple_debug_info("sipe", "got %s\n", from);
1127 return from;
1130 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1132 va_list args;
1133 xmlnode * node = NULL;
1134 const gchar * name;
1136 va_start(args, parent);
1137 while ((name = va_arg(args, const char *)) != NULL) {
1138 node = xmlnode_get_child(parent, name);
1139 if (node == NULL) return NULL;
1140 parent = node;
1142 va_end(args);
1144 return node;
1148 static void
1149 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1151 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1152 send_soap_request(sip, body);
1153 g_free(body);
1156 static void
1157 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1159 if (allow) {
1160 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1161 } else {
1162 purple_debug_info("sipe", "Blocking contact %s\n", who);
1165 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1168 static
1169 void sipe_auth_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, TRUE);
1175 g_free(job);
1178 static
1179 void sipe_deny_user_cb(void * data)
1181 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1182 if (!job) return;
1184 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1185 g_free(job);
1188 static void
1189 sipe_add_permit(PurpleConnection *gc, const char *name)
1191 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1192 sipe_contact_allow_deny(sip, name, TRUE);
1195 static void
1196 sipe_add_deny(PurpleConnection *gc, const char *name)
1198 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1199 sipe_contact_allow_deny(sip, name, FALSE);
1202 /*static void
1203 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1205 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1206 sipe_contact_set_acl(sip, name, "");
1209 static void
1210 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1212 xmlnode *watchers;
1213 xmlnode *watcher;
1214 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1215 if (msg->response != 0 && msg->response != 200) return;
1217 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1219 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1220 if (!watchers) return;
1222 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1223 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1224 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1225 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1227 // TODO pull out optional displayName to pass as alias
1228 if (remote_user) {
1229 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1230 job->who = remote_user;
1231 job->sip = sip;
1232 purple_account_request_authorization(
1233 sip->account,
1234 remote_user,
1235 NULL, // id
1236 alias,
1237 NULL, // message
1238 on_list,
1239 sipe_auth_user_cb,
1240 sipe_deny_user_cb,
1241 (void *) job);
1246 xmlnode_free(watchers);
1247 return;
1250 static void
1251 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1253 PurpleGroup * purple_group = purple_find_group(group->name);
1254 if (!purple_group) {
1255 purple_group = purple_group_new(group->name);
1256 purple_blist_add_group(purple_group, NULL);
1259 if (purple_group) {
1260 group->purple_group = purple_group;
1261 sip->groups = g_slist_append(sip->groups, group);
1262 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1263 } else {
1264 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1268 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1270 struct sipe_group *group;
1271 GSList *entry;
1272 if (sip == NULL) {
1273 return NULL;
1276 entry = sip->groups;
1277 while (entry) {
1278 group = entry->data;
1279 if (group->id == id) {
1280 return group;
1282 entry = entry->next;
1284 return NULL;
1287 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1289 struct sipe_group *group;
1290 GSList *entry;
1291 if (sip == NULL) {
1292 return NULL;
1295 entry = sip->groups;
1296 while (entry) {
1297 group = entry->data;
1298 if (!strcmp(group->name, name)) {
1299 return group;
1301 entry = entry->next;
1303 return NULL;
1306 static void
1307 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1309 gchar *body;
1310 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1311 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1312 send_soap_request(sip, body);
1313 g_free(body);
1314 g_free(group->name);
1315 group->name = g_strdup(name);
1319 * Only appends if no such value already stored.
1320 * Like Set in Java.
1322 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1323 GSList * res = list;
1324 if (!g_slist_find_custom(list, data, func)) {
1325 res = g_slist_insert_sorted(list, data, func);
1327 return res;
1330 static int
1331 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1332 return group1->id - group2->id;
1336 * Returns string like "2 4 7 8" - group ids buddy belong to.
1338 static gchar *
1339 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1340 int i = 0;
1341 gchar *res;
1342 //creating array from GList, converting int to gchar*
1343 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1344 GSList *entry = buddy->groups;
1345 while (entry) {
1346 struct sipe_group * group = entry->data;
1347 ids_arr[i] = g_strdup_printf("%d", group->id);
1348 entry = entry->next;
1349 i++;
1351 ids_arr[i] = NULL;
1352 res = g_strjoinv(" ", ids_arr);
1353 g_strfreev(ids_arr);
1354 return res;
1358 * Sends buddy update to server
1360 static void
1361 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1363 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1364 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1366 if (buddy && purple_buddy) {
1367 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1368 gchar *body;
1369 gchar *groups = sipe_get_buddy_groups_string(buddy);
1370 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1372 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1373 alias, groups, "true", buddy->name, sip->contacts_delta++
1375 send_soap_request(sip, body);
1376 g_free(groups);
1377 g_free(body);
1381 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1383 if (msg->response == 200) {
1384 struct sipe_group *group;
1385 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1386 xmlnode *xml;
1387 xmlnode *node;
1388 char *group_id;
1389 struct sipe_buddy *buddy;
1391 xml = xmlnode_from_str(msg->body, msg->bodylen);
1392 if (!xml) {
1393 g_free(ctx);
1394 return FALSE;
1397 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1398 if (!node) {
1399 g_free(ctx);
1400 xmlnode_free(xml);
1401 return FALSE;
1404 group_id = xmlnode_get_data(node);
1405 if (!group_id) {
1406 g_free(ctx);
1407 xmlnode_free(xml);
1408 return FALSE;
1411 group = g_new0(struct sipe_group, 1);
1412 group->id = (int)g_ascii_strtod(group_id, NULL);
1413 g_free(group_id);
1414 group->name = ctx->group_name;
1416 sipe_group_add(sip, group);
1418 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1419 if (buddy) {
1420 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1423 sipe_group_set_user(sip, ctx->user_name);
1425 g_free(ctx);
1426 xmlnode_free(xml);
1427 return TRUE;
1429 return FALSE;
1432 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1434 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1435 gchar *body;
1436 ctx->group_name = g_strdup(name);
1437 ctx->user_name = g_strdup(who);
1439 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1440 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1441 g_free(body);
1445 * A timer callback
1446 * Should return FALSE if repetitive action is not needed
1448 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1450 gboolean ret;
1451 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1452 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1453 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1454 (sched_action->action)(sched_action->sip, sched_action->payload);
1455 ret = sched_action->repetitive;
1456 g_free(sched_action->payload);
1457 g_free(sched_action->name);
1458 g_free(sched_action);
1459 return ret;
1463 * Do schedule action for execution in the future.
1464 * Non repetitive execution.
1466 * @param name of action (will be copied)
1467 * @param timeout in seconds
1468 * @action callback function
1469 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1471 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1473 struct scheduled_action *sched_action;
1475 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1476 sched_action = g_new0(struct scheduled_action, 1);
1477 sched_action->repetitive = FALSE;
1478 sched_action->name = g_strdup(name);
1479 sched_action->action = action;
1480 sched_action->sip = sip;
1481 sched_action->payload = payload;
1482 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1483 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1484 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1488 * Kills action timer effectively cancelling
1489 * scheduled action
1491 * @param name of action
1493 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1495 GSList *entry;
1497 if (!sip->timeouts || !name) return;
1499 entry = sip->timeouts;
1500 while (entry) {
1501 struct scheduled_action *sched_action = entry->data;
1502 if(!strcmp(sched_action->name, name)) {
1503 GSList *to_delete = entry;
1504 entry = entry->next;
1505 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1506 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1507 purple_timeout_remove(sched_action->timeout_handler);
1508 g_free(sched_action->payload);
1509 g_free(sched_action->name);
1510 g_free(sched_action);
1511 } else {
1512 entry = entry->next;
1517 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1519 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1521 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1523 process_incoming_notify(sip, msg, FALSE, FALSE);
1525 return TRUE;
1528 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1530 gchar *tmp = *resources_uri;
1531 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1532 g_free(tmp);
1535 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1537 gchar *tmp = *resources_uri;
1538 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1539 g_free(tmp);
1543 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1544 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1545 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1546 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1547 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1550 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip){
1551 gchar *to = g_strdup_printf("sip:%s", sip->username);
1552 gchar *contact = get_contact(sip);
1553 gchar *request;
1554 gchar *content;
1555 gchar *resources_uri = g_strdup("");
1556 gchar *require = "";
1557 gchar *accept = "";
1558 gchar *autoextend = "";
1559 gchar *content_type;
1562 if (sip->msrtc_event_categories) {
1563 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1564 require = ", categoryList";
1565 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1566 content_type = "application/msrtc-adrl-categorylist+xml";
1567 content = g_strdup_printf(
1568 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1569 "<action name=\"subscribe\" id=\"63792024\">\n"
1570 "<adhocList>\n%s</adhocList>\n"
1571 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1572 "<category name=\"note\"/>\n"
1573 "<category name=\"state\"/>\n"
1574 "</categoryList>\n"
1575 "</action>\n"
1576 "</batchSub>", sip->username, resources_uri);
1577 } else {
1578 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1579 autoextend = "Supported: com.microsoft.autoextend\r\n";
1580 content_type = "application/adrl+xml";
1581 content = g_strdup_printf(
1582 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1583 "<create xmlns=\"\">\n%s</create>\n"
1584 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1586 g_free(resources_uri);
1588 request = g_strdup_printf(
1589 "Require: adhoclist%s\r\n"
1590 "Supported: eventlist\r\n"
1591 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1592 "Supported: ms-piggyback-first-notify\r\n"
1593 "%sSupported: ms-benotify\r\n"
1594 "Proxy-Require: ms-benotify\r\n"
1595 "Event: presence\r\n"
1596 "Content-Type: %s\r\n"
1597 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1598 g_free(contact);
1600 /* subscribe to buddy presence */
1601 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1602 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1604 g_free(content);
1605 g_free(to);
1606 g_free(request);
1610 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1611 * The user sends a single SUBSCRIBE request to the subscribed contact.
1612 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1616 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1618 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1619 gchar *tmp = get_contact(sip);
1620 gchar *request;
1621 gchar *content;
1622 request = g_strdup_printf(
1623 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1624 "Supported: ms-piggyback-first-notify\r\n"
1625 "Supported: com.microsoft.autoextend\r\n"
1626 "Supported: ms-benotify\r\n"
1627 "Proxy-Require: ms-benotify\r\n"
1628 "Event: presence\r\n"
1629 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1630 "Contact: %s\r\n", tmp);
1632 content = g_strdup_printf(
1633 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1634 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1635 "<resource uri=\"%s\"/>\n"
1636 "</adhocList>\n"
1637 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1638 "<category name=\"note\"/>\n"
1639 "<category name=\"state\"/>\n"
1640 "</categoryList>\n"
1641 "</action>\n"
1642 "</batchSub>", sip->username, to
1645 g_free(tmp);
1647 /* subscribe to buddy presence */
1648 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1650 g_free(content);
1651 g_free(to);
1652 g_free(request);
1655 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1657 if (!purple_status_is_active(status))
1658 return;
1660 if (account->gc) {
1661 struct sipe_account_data *sip = account->gc->proto_data;
1663 if (sip) {
1664 g_free(sip->status);
1665 sip->status = g_strdup(purple_status_get_id(status));
1666 send_presence_status(sip);
1671 static void
1672 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1674 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1675 sipe_group_set_user(sip, name);
1678 static void
1679 sipe_group_buddy(PurpleConnection *gc,
1680 const char *who,
1681 const char *old_group_name,
1682 const char *new_group_name)
1684 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1685 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1686 struct sipe_group * old_group = NULL;
1687 struct sipe_group * new_group;
1689 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1690 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1692 if(!buddy) { // buddy not in roaming list
1693 return;
1696 if (old_group_name) {
1697 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1699 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1701 if (old_group) {
1702 buddy->groups = g_slist_remove(buddy->groups, old_group);
1703 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1706 if (!new_group) {
1707 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1708 } else {
1709 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1710 sipe_group_set_user(sip, who);
1714 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1716 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1717 struct sipe_buddy *b;
1719 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1721 // Prepend sip: if needed
1722 if (strncmp("sip:", buddy->name, 4)) {
1723 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1724 purple_blist_rename_buddy(buddy, buf);
1725 g_free(buf);
1728 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1729 b = g_new0(struct sipe_buddy, 1);
1730 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1731 b->name = g_strdup(buddy->name);
1732 g_hash_table_insert(sip->buddies, b->name, b);
1733 sipe_group_buddy(gc, b->name, NULL, group->name);
1734 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1735 } else {
1736 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1740 static void sipe_free_buddy(struct sipe_buddy *buddy)
1742 g_free(buddy->name);
1743 g_free(buddy->annotation);
1744 g_free(buddy->device_name);
1745 g_slist_free(buddy->groups);
1746 g_free(buddy);
1750 * Unassociates buddy from group first.
1751 * Then see if no groups left, removes buddy completely.
1752 * Otherwise updates buddy groups on server.
1754 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1756 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1757 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1758 struct sipe_group *g = NULL;
1760 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1762 if (!b) return;
1764 if (group) {
1765 g = sipe_group_find_by_name(sip, group->name);
1768 if (g) {
1769 b->groups = g_slist_remove(b->groups, g);
1770 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1773 if (g_slist_length(b->groups) < 1) {
1774 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1775 sipe_cancel_scheduled_action(sip, action_name);
1776 g_free(action_name);
1778 g_hash_table_remove(sip->buddies, buddy->name);
1780 if (b->name) {
1781 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1782 send_soap_request(sip, body);
1783 g_free(body);
1786 sipe_free_buddy(b);
1787 } else {
1788 //updates groups on server
1789 sipe_group_set_user(sip, b->name);
1794 static void
1795 sipe_rename_group(PurpleConnection *gc,
1796 const char *old_name,
1797 PurpleGroup *group,
1798 GList *moved_buddies)
1800 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1801 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1802 if (group) {
1803 sipe_group_rename(sip, s_group, group->name);
1804 } else {
1805 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1809 static void
1810 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1812 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1813 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1814 if (s_group) {
1815 gchar *body;
1816 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1817 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1818 send_soap_request(sip, body);
1819 g_free(body);
1821 sip->groups = g_slist_remove(sip->groups, s_group);
1822 g_free(s_group->name);
1823 g_free(s_group);
1824 } else {
1825 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1829 static GList *sipe_status_types(PurpleAccount *acc)
1831 PurpleStatusType *type;
1832 GList *types = NULL;
1834 // Online
1835 type = purple_status_type_new_with_attrs(
1836 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1837 // Translators: noun
1838 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1839 NULL);
1840 types = g_list_append(types, type);
1842 // Busy
1843 type = purple_status_type_new_with_attrs(
1844 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1845 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1846 NULL);
1847 types = g_list_append(types, type);
1849 // Do Not Disturb (not user settable)
1850 type = purple_status_type_new_with_attrs(
1851 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1852 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1853 NULL);
1854 types = g_list_append(types, type);
1856 // Be Right Back
1857 type = purple_status_type_new_with_attrs(
1858 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1859 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1860 NULL);
1861 types = g_list_append(types, type);
1863 // Away
1864 type = purple_status_type_new_with_attrs(
1865 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1866 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1867 NULL);
1868 types = g_list_append(types, type);
1870 //On The Phone
1871 type = purple_status_type_new_with_attrs(
1872 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1873 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1874 NULL);
1875 types = g_list_append(types, type);
1877 //Out To Lunch
1878 type = purple_status_type_new_with_attrs(
1879 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1880 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1881 NULL);
1882 types = g_list_append(types, type);
1884 //Appear Offline
1885 type = purple_status_type_new_full(
1886 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1887 types = g_list_append(types, type);
1889 // Offline
1890 type = purple_status_type_new_full(
1891 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1892 types = g_list_append(types, type);
1894 return types;
1898 * A callback for g_hash_table_foreach
1900 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1902 sipe_subscribe_presence_single(sip, buddy->name);
1906 * Removes entries from purple buddy list
1907 * that does not correspond ones in the roaming contact list.
1909 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1910 GSList *buddies = purple_find_buddies(sip->account, NULL);
1911 GSList *entry = buddies;
1912 struct sipe_buddy *buddy;
1913 PurpleBuddy *b;
1914 PurpleGroup *g;
1916 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1917 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1918 while (entry) {
1919 b = entry->data;
1920 g = purple_buddy_get_group(b);
1921 buddy = g_hash_table_lookup(sip->buddies, b->name);
1922 if(buddy) {
1923 gboolean in_sipe_groups = FALSE;
1924 GSList *entry2 = buddy->groups;
1925 while (entry2) {
1926 struct sipe_group *group = entry2->data;
1927 if (!strcmp(group->name, g->name)) {
1928 in_sipe_groups = TRUE;
1929 break;
1931 entry2 = entry2->next;
1933 if(!in_sipe_groups) {
1934 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1935 purple_blist_remove_buddy(b);
1937 } else {
1938 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1939 purple_blist_remove_buddy(b);
1941 entry = entry->next;
1945 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1947 int len = msg->bodylen;
1949 gchar *tmp = sipmsg_find_header(msg, "Event");
1950 xmlnode *item;
1951 xmlnode *isc;
1952 const gchar *contacts_delta;
1953 xmlnode *group_node;
1954 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1955 return FALSE;
1958 /* Convert the contact from XML to Purple Buddies */
1959 isc = xmlnode_from_str(msg->body, len);
1960 if (!isc) {
1961 return FALSE;
1964 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1965 if (contacts_delta) {
1966 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1969 /* Parse groups */
1970 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1971 struct sipe_group * group = g_new0(struct sipe_group, 1);
1972 const char *name = xmlnode_get_attrib(group_node, "name");
1974 if (!strncmp(name, "~", 1)) {
1975 // TODO translate
1976 name = "Other Contacts";
1978 group->name = g_strdup(name);
1979 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1981 sipe_group_add(sip, group);
1984 // Make sure we have at least one group
1985 if (g_slist_length(sip->groups) == 0) {
1986 struct sipe_group * group = g_new0(struct sipe_group, 1);
1987 PurpleGroup *purple_group;
1988 // TODO translate
1989 group->name = g_strdup("Other Contacts");
1990 group->id = 1;
1991 purple_group = purple_group_new(group->name);
1992 purple_blist_add_group(purple_group, NULL);
1993 sip->groups = g_slist_append(sip->groups, group);
1996 /* Parse contacts */
1997 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1998 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1999 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
2000 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
2001 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
2002 gchar **item_groups;
2003 struct sipe_group *group = NULL;
2004 struct sipe_buddy *buddy = NULL;
2005 int i = 0;
2007 // assign to group Other Contacts if nothing else received
2008 if(!groups || !strcmp("", groups) ) {
2009 group = sipe_group_find_by_name(sip, "Other Contacts");
2010 groups = group ? g_strdup_printf("%d", group->id) : "1";
2013 item_groups = g_strsplit(groups, " ", 0);
2015 while (item_groups[i]) {
2016 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2018 // If couldn't find the right group for this contact, just put them in the first group we have
2019 if (group == NULL && g_slist_length(sip->groups) > 0) {
2020 group = sip->groups->data;
2023 if (group != NULL) {
2024 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2025 if (!b){
2026 b = purple_buddy_new(sip->account, buddy_name, uri);
2027 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2030 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2031 if (name != NULL && strlen(name) != 0) {
2032 purple_blist_alias_buddy(b, name);
2036 if (!buddy) {
2037 buddy = g_new0(struct sipe_buddy, 1);
2038 buddy->name = g_strdup(b->name);
2039 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2042 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2044 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2045 } else {
2046 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2047 name);
2050 i++;
2051 } // while, contact groups
2052 g_strfreev(item_groups);
2053 g_free(groups);
2054 g_free(name);
2055 g_free(buddy_name);
2056 g_free(uri);
2058 } // for, contacts
2060 xmlnode_free(isc);
2062 sipe_cleanup_local_blist(sip);
2064 //subscribe to buddies
2065 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2066 //if(sip->msrtc_event_categories){
2067 sipe_subscribe_presence_batched(sip);
2068 //}else{
2069 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2071 sip->subscribed_buddies = TRUE;
2074 return 0;
2078 * Subscribe roaming contacts
2080 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2082 gchar *to = g_strdup_printf("sip:%s", sip->username);
2083 gchar *tmp = get_contact(sip);
2084 gchar *hdr = g_strdup_printf(
2085 "Event: vnd-microsoft-roaming-contacts\r\n"
2086 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2087 "Supported: com.microsoft.autoextend\r\n"
2088 "Supported: ms-benotify\r\n"
2089 "Proxy-Require: ms-benotify\r\n"
2090 "Supported: ms-piggyback-first-notify\r\n"
2091 "Contact: %s\r\n", tmp);
2092 g_free(tmp);
2094 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2095 g_free(to);
2096 g_free(hdr);
2099 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2101 gchar *to = g_strdup_printf("sip:%s", sip->username);
2102 gchar *tmp = get_contact(sip);
2103 gchar *hdr = g_strdup_printf(
2104 "Event: presence.wpending\r\n"
2105 "Accept: text/xml+msrtc.wpending\r\n"
2106 "Supported: com.microsoft.autoextend\r\n"
2107 "Supported: ms-benotify\r\n"
2108 "Proxy-Require: ms-benotify\r\n"
2109 "Supported: ms-piggyback-first-notify\r\n"
2110 "Contact: %s\r\n", tmp);
2111 g_free(tmp);
2113 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2114 g_free(to);
2115 g_free(hdr);
2119 * Fires on deregistration event initiated by server.
2120 * [MS-SIPREGE] SIP extension.
2123 // 2007 Example
2125 // Content-Type: text/registration-event
2126 // subscription-state: terminated;expires=0
2127 // ms-diagnostics-public: 4141;reason="User disabled"
2129 // deregistered;event=rejected
2131 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2133 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2134 gchar *event = NULL;
2135 gchar *reason = NULL;
2136 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2138 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2139 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2141 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2142 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2143 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2144 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2145 } else {
2146 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2147 return;
2150 if (warning != NULL) {
2151 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2152 } else { // for LCS2005
2153 int error_id = 0;
2154 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2155 error_id = 4140; // [MS-SIPREGE]
2156 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2157 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2158 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2159 error_id = 4141;
2160 reason = g_strdup(_("User disabled")); // [MS-OCER]
2161 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2162 error_id = 4142;
2163 reason = g_strdup(_("User moved")); // [MS-OCER]
2166 g_free(event);
2167 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2168 g_free(reason);
2170 sip->gc->wants_to_die = TRUE;
2171 purple_connection_error(sip->gc, warning);
2172 g_free(warning);
2176 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2178 const gchar *contacts_delta;
2179 xmlnode *xml;
2181 xml = xmlnode_from_str(msg->body, msg->bodylen);
2182 if (!xml)
2184 return;
2187 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2188 if (contacts_delta)
2190 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2193 xmlnode_free(xml);
2199 * When we receive some self (BE) NOTIFY with a new subscriber
2200 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2204 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2206 gchar *contact;
2207 gchar *to;
2208 xmlnode *xml;
2209 xmlnode *node;
2210 char *display_name = NULL;
2211 PurpleBuddy *pbuddy;
2212 const char *alias;
2213 char *uri_alias;
2214 char *uri_user;
2216 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2218 xml = xmlnode_from_str(msg->body, msg->bodylen);
2219 if (!xml) return;
2221 contact = get_contact(sip);
2222 to = g_strdup_printf("sip:%s", sip->username);
2224 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2225 const char *user;
2226 const char *acknowledged;
2227 gchar *hdr;
2228 gchar *body;
2230 user = xmlnode_get_attrib(node, "user");
2231 if (!user) continue;
2232 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2233 uri_user = g_strdup_printf("sip:%s", user);
2234 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2235 if(pbuddy){
2236 alias = purple_buddy_get_local_alias(pbuddy);
2237 uri_alias = g_strdup_printf("sip:%s", alias);
2238 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2239 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2240 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2241 purple_blist_alias_buddy(pbuddy, display_name);
2243 g_free(uri_alias);
2245 g_free(uri_user);
2247 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2248 if(!g_ascii_strcasecmp(acknowledged,"false")){
2249 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2250 hdr = g_strdup_printf(
2251 "Contact: %s\r\n"
2252 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2254 body = g_strdup_printf(
2255 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2256 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2257 "</setSubscribers>", user);
2259 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2260 g_free(body);
2261 g_free(hdr);
2265 g_free(to);
2266 g_free(contact);
2267 xmlnode_free(xml);
2270 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2272 gchar *to = g_strdup_printf("sip:%s", sip->username);
2273 gchar *tmp = get_contact(sip);
2274 gchar *hdr = g_strdup_printf(
2275 "Event: vnd-microsoft-roaming-ACL\r\n"
2276 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2277 "Supported: com.microsoft.autoextend\r\n"
2278 "Supported: ms-benotify\r\n"
2279 "Proxy-Require: ms-benotify\r\n"
2280 "Supported: ms-piggyback-first-notify\r\n"
2281 "Contact: %s\r\n", tmp);
2282 g_free(tmp);
2284 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2285 g_free(to);
2286 g_free(hdr);
2290 * To request for presence information about the user, access level settings that have already been configured by the user
2291 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2292 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2295 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2297 gchar *to = g_strdup_printf("sip:%s", sip->username);
2298 gchar *tmp = get_contact(sip);
2299 gchar *hdr = g_strdup_printf(
2300 "Event: vnd-microsoft-roaming-self\r\n"
2301 "Accept: application/vnd-microsoft-roaming-self+xml\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"
2306 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2308 gchar *body=g_strdup(
2309 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2310 "<roaming type=\"categories\"/>"
2311 "<roaming type=\"containers\"/>"
2312 "<roaming type=\"subscribers\"/></roamingList>");
2314 g_free(tmp);
2315 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2316 g_free(body);
2317 g_free(to);
2318 g_free(hdr);
2322 * For 2005 version
2324 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2326 gchar *to = g_strdup_printf("sip:%s", sip->username);
2327 gchar *tmp = get_contact(sip);
2328 gchar *hdr = g_strdup_printf(
2329 "Event: vnd-microsoft-provisioning\r\n"
2330 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2331 "Supported: com.microsoft.autoextend\r\n"
2332 "Supported: ms-benotify\r\n"
2333 "Proxy-Require: ms-benotify\r\n"
2334 "Supported: ms-piggyback-first-notify\r\n"
2335 "Expires: 0\r\n"
2336 "Contact: %s\r\n", tmp);
2338 g_free(tmp);
2339 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2340 g_free(to);
2341 g_free(hdr);
2344 /** Subscription for provisioning information to help with initial
2345 * configuration. This subscription is a one-time query (denoted by the Expires header,
2346 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2347 * configuration, meeting policies, and policy settings that Communicator must enforce.
2348 * TODO: for what we need this information.
2351 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2353 gchar *to = g_strdup_printf("sip:%s", sip->username);
2354 gchar *tmp = get_contact(sip);
2355 gchar *hdr = g_strdup_printf(
2356 "Event: vnd-microsoft-provisioning-v2\r\n"
2357 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2358 "Supported: com.microsoft.autoextend\r\n"
2359 "Supported: ms-benotify\r\n"
2360 "Proxy-Require: ms-benotify\r\n"
2361 "Supported: ms-piggyback-first-notify\r\n"
2362 "Expires: 0\r\n"
2363 "Contact: %s\r\n"
2364 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2365 gchar *body = g_strdup(
2366 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2367 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2368 "<provisioningGroup name=\"ucPolicy\"/>"
2369 "</provisioningGroupList>");
2371 g_free(tmp);
2372 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2373 g_free(body);
2374 g_free(to);
2375 g_free(hdr);
2378 /* IM Session (INVITE and MESSAGE methods) */
2380 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2382 struct sip_im_session *session;
2383 GSList *entry;
2384 if (sip == NULL || who == NULL) {
2385 return NULL;
2388 entry = sip->im_sessions;
2389 while (entry) {
2390 session = entry->data;
2391 if ((who != NULL && !strcmp(who, session->with))) {
2392 return session;
2394 entry = entry->next;
2396 return NULL;
2399 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2401 struct sip_im_session *session = find_im_session(sip, who);
2402 if (!session) {
2403 session = g_new0(struct sip_im_session, 1);
2404 session->with = g_strdup(who);
2405 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2406 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2408 return session;
2411 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2413 struct sip_dialog *dialog = session->dialog;
2414 GSList *entry;
2416 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2418 if (dialog) {
2419 entry = dialog->routes;
2420 while (entry) {
2421 g_free(entry->data);
2422 entry = g_slist_remove(entry, entry->data);
2424 entry = dialog->supported;
2425 while (entry) {
2426 g_free(entry->data);
2427 entry = g_slist_remove(entry, entry->data);
2429 g_free(dialog->callid);
2430 g_free(dialog->ourtag);
2431 g_free(dialog->theirtag);
2432 g_free(dialog->theirepid);
2433 g_free(dialog->request);
2435 g_free(session->dialog);
2437 entry = session->outgoing_message_queue;
2438 while (entry) {
2439 g_free(entry->data);
2440 entry = g_slist_remove(entry, entry->data);
2443 g_hash_table_destroy(session->unconfirmed_messages);
2445 g_free(session->with);
2446 g_free(session);
2449 static gboolean
2450 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2452 gboolean ret = TRUE;
2454 if (msg->response != 200) {
2455 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2456 return FALSE;
2459 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2461 return ret;
2465 * Asks UA/proxy about its capabilities.
2467 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2469 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2470 gchar *contact = get_contact(sip);
2471 gchar *request;
2472 request = g_strdup_printf(
2473 "Accept: application/sdp\r\n"
2474 "Contact: %s\r\n", contact);
2476 g_free(contact);
2478 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2480 g_free(to);
2481 g_free(request);
2484 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2486 char *msg, *msg_tmp;
2487 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2488 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2489 g_free(msg_tmp);
2490 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2491 "possibly because one or more persons are offline:\n%s") ,
2492 msg ? msg : "");
2493 purple_conv_present_error(with, sip->account, msg_tmp);
2494 g_free(msg);
2495 g_free(msg_tmp);
2498 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2500 static gboolean
2501 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2503 gboolean ret = TRUE;
2504 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2505 struct sip_im_session * session = find_im_session(sip, with);
2506 struct sip_dialog *dialog;
2507 gchar *cseq;
2508 char *key;
2509 gchar *message;
2511 if (!session) {
2512 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2513 g_free(with);
2514 return FALSE;
2517 dialog = session->dialog;
2518 if (!dialog) {
2519 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2520 g_free(with);
2521 return FALSE;
2524 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2525 key = g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq));
2526 g_free(cseq);
2527 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2529 if (msg->response != 200) {
2530 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2532 sipe_present_message_undelivered_err(with, sip, message);
2533 im_session_destroy(sip, session);
2534 ret = FALSE;
2535 } else {
2536 g_hash_table_remove(session->unconfirmed_messages, key);
2537 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2538 key, g_hash_table_size(session->unconfirmed_messages));
2541 g_free(key);
2542 g_free(with);
2544 sipe_im_process_queue(sip, session);
2545 return ret;
2548 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2550 gchar *hdr;
2551 gchar *fullto;
2552 gchar *tmp;
2553 char *msgformat;
2554 char *msgtext;
2555 gchar *msgr_value;
2556 gchar *msgr;
2558 if (strncmp("sip:", session->with, 4)) {
2559 fullto = g_strdup_printf("sip:%s", session->with);
2560 } else {
2561 fullto = g_strdup(session->with);
2564 sipe_parse_html(msg, &msgformat, &msgtext);
2565 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2567 msgr_value = sipmsg_get_msgr_string(msgformat);
2568 g_free(msgformat);
2569 if (msgr_value) {
2570 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2571 g_free(msgr_value);
2572 } else {
2573 msgr = g_strdup("");
2576 tmp = get_contact(sip);
2577 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2578 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2579 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2580 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2581 tmp, msgr);
2582 g_free(tmp);
2583 g_free(msgr);
2585 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2586 g_free(msgtext);
2587 g_free(hdr);
2588 g_free(fullto);
2592 static void
2593 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2595 GSList *entry = session->outgoing_message_queue;
2597 if (session->outgoing_invite) return; //do not send messages until INVITE responded.
2599 while (entry) {
2600 char *key = g_strdup_printf("<%s><%d><MESSAGE>", session->dialog->callid, (session->dialog->cseq) + 1);
2601 char *queued_msg = entry->data;
2602 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2603 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2604 key, g_hash_table_size(session->unconfirmed_messages));
2605 g_free(key);
2606 sipe_send_message(sip, session, queued_msg);
2607 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2608 g_free(queued_msg);
2612 static void
2613 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2615 GSList *hdr = msg->headers;
2616 struct siphdrelement *elem;
2617 gchar *contact;
2619 while(hdr)
2621 elem = hdr->data;
2622 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2624 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2625 dialog->routes = g_slist_append(dialog->routes, route);
2627 hdr = g_slist_next(hdr);
2630 if (outgoing)
2632 dialog->routes = g_slist_reverse(dialog->routes);
2635 if (dialog->routes)
2637 dialog->request = dialog->routes->data;
2638 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2641 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2642 dialog->routes = g_slist_append(dialog->routes, contact);
2645 static void
2646 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2648 GSList *hdr = msg->headers;
2649 struct siphdrelement *elem;
2650 while(hdr)
2652 elem = hdr->data;
2653 if(!g_ascii_strcasecmp(elem->name, "Supported")
2654 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2656 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2659 hdr = g_slist_next(hdr);
2663 static void
2664 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2666 gchar *us = outgoing ? "From" : "To";
2667 gchar *them = outgoing ? "To" : "From";
2669 g_free(dialog->callid);
2670 g_free(dialog->ourtag);
2671 g_free(dialog->theirtag);
2673 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2674 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2675 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2676 if (!dialog->theirepid) {
2677 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2678 if (!dialog->theirepid) {
2679 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2683 sipe_get_route_header(msg, dialog, outgoing);
2684 sipe_get_supported_header(msg, dialog, outgoing);
2688 static gboolean
2689 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2691 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2692 struct sip_im_session * session = find_im_session(sip, with);
2693 struct sip_dialog *dialog;
2694 char *cseq;
2695 char *key;
2696 gchar *message;
2698 if (!session) {
2699 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2700 g_free(with);
2701 return FALSE;
2704 dialog = session->dialog;
2705 if (!dialog) {
2706 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2707 g_free(with);
2708 return FALSE;
2711 sipe_parse_dialog(msg, dialog, TRUE);
2713 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2714 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2715 g_free(cseq);
2716 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2718 if (msg->response != 200) {
2719 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2721 sipe_present_message_undelivered_err(with, sip, message);
2722 im_session_destroy(sip, session);
2723 g_free(with);
2724 return FALSE;
2727 dialog->cseq = 0;
2728 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2729 session->outgoing_invite = NULL;
2730 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2731 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2732 if (session->outgoing_message_queue) {
2733 char *queued_msg = session->outgoing_message_queue->data;
2734 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2735 g_free(queued_msg);
2739 sipe_im_process_queue(sip, session);
2741 g_hash_table_remove(session->unconfirmed_messages, key);
2742 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2743 key, g_hash_table_size(session->unconfirmed_messages));
2745 g_free(key);
2746 g_free(with);
2747 return TRUE;
2751 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session *session, const gchar *msg_body)
2753 gchar *hdr;
2754 gchar *to;
2755 gchar *contact;
2756 gchar *body;
2757 char *msgformat;
2758 char *msgtext;
2759 char *base64_msg;
2760 char *ms_text_format;
2761 gchar *msgr_value;
2762 gchar *msgr;
2763 char *key;
2765 if (session->dialog) {
2766 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2767 return;
2770 session->dialog = g_new0(struct sip_dialog, 1);
2771 session->dialog->callid = gencallid();
2773 if (strstr(session->with, "sip:")) {
2774 to = g_strdup(session->with);
2775 } else {
2776 to = g_strdup_printf("sip:%s", session->with);
2779 sipe_parse_html(msg_body, &msgformat, &msgtext);
2780 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2782 msgr_value = sipmsg_get_msgr_string(msgformat);
2783 g_free(msgformat);
2784 msgr = "";
2785 if (msgr_value) {
2786 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2787 g_free(msgr_value);
2790 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2791 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2792 g_free(msgtext);
2793 g_free(msgr);
2794 g_free(base64_msg);
2796 key = g_strdup_printf("<%s><%d><INVITE>", session->dialog->callid, (session->dialog->cseq) + 1);
2797 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2798 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2799 key, g_hash_table_size(session->unconfirmed_messages));
2800 g_free(key);
2802 contact = get_contact(sip);
2803 hdr = g_strdup_printf(
2804 "Contact: %s\r\n%s"
2805 "Content-Type: application/sdp\r\n",
2806 contact, ms_text_format);
2807 g_free(ms_text_format);
2809 body = g_strdup_printf(
2810 "v=0\r\n"
2811 "o=- 0 0 IN IP4 %s\r\n"
2812 "s=session\r\n"
2813 "c=IN IP4 %s\r\n"
2814 "t=0 0\r\n"
2815 "m=message %d sip null\r\n"
2816 "a=accept-types:text/plain text/html image/gif "
2817 "multipart/alternative application/im-iscomposing+xml\r\n",
2818 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2820 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2821 to, to, hdr, body, session->dialog, process_invite_response);
2823 g_free(to);
2824 g_free(body);
2825 g_free(hdr);
2826 g_free(contact);
2829 static void
2830 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2832 if (session) {
2833 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2834 im_session_destroy(sip, session);
2838 static void
2839 sipe_convo_closed(PurpleConnection * gc, const char *who)
2841 struct sipe_account_data *sip = gc->proto_data;
2843 purple_debug_info("sipe", "conversation with %s closed\n", who);
2844 im_session_close(sip, find_im_session(sip, who));
2847 static void
2848 im_session_close_all (struct sipe_account_data *sip)
2850 GSList *entry = sip->im_sessions;
2851 while (entry) {
2852 im_session_close (sip, entry->data);
2853 entry = sip->im_sessions;
2857 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2859 struct sipe_account_data *sip;
2860 gchar *to;
2861 struct sip_im_session *session;
2863 sip = gc->proto_data;
2864 to = g_strdup(who);
2866 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2868 session = find_or_create_im_session(sip, who);
2870 // Queue the message
2871 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
2873 if (session->dialog && session->dialog->callid) {
2874 sipe_im_process_queue(sip, session);
2875 } else if (!session->outgoing_invite) {
2876 // Need to send the INVITE to get the outgoing dialog setup
2877 sipe_invite(sip, session, what);
2880 g_free(to);
2881 return 1;
2884 /* End IM Session (INVITE and MESSAGE methods) */
2886 static unsigned int
2887 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2889 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2890 struct sip_im_session *session;
2892 if (state == PURPLE_NOT_TYPING)
2893 return 0;
2895 session = find_im_session(sip, who);
2897 if (session && session->dialog) {
2898 send_sip_request(gc, "INFO", who, who,
2899 "Content-Type: application/xml\r\n",
2900 SIPE_SEND_TYPING, session->dialog, NULL);
2902 return SIPE_TYPING_SEND_TIMEOUT;
2905 static gboolean resend_timeout(struct sipe_account_data *sip)
2907 GSList *tmp = sip->transactions;
2908 time_t currtime = time(NULL);
2909 while (tmp) {
2910 struct transaction *trans = tmp->data;
2911 tmp = tmp->next;
2912 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2913 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2914 /* TODO 408 */
2915 } else {
2916 if ((currtime - trans->time > 2) && trans->retries == 0) {
2917 trans->retries++;
2918 sendout_sipmsg(sip, trans->msg);
2922 return TRUE;
2925 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2927 /* register again when security token expires */
2928 /* we have to start a new authentication as the security token
2929 * is almost expired by sending a not signed REGISTER message */
2930 purple_debug_info("sipe", "do a full reauthentication\n");
2931 sipe_auth_free(&sip->registrar);
2932 sip->registerstatus = 0;
2933 do_register(sip);
2934 sip->reauthenticate_set = FALSE;
2937 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2939 gchar *from;
2940 gchar *contenttype;
2941 gboolean found = FALSE;
2943 from = parse_from(sipmsg_find_header(msg, "From"));
2945 if (!from) return;
2947 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2949 contenttype = sipmsg_find_header(msg, "Content-Type");
2950 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2952 gchar *html = get_html_message(contenttype, msg->body);
2953 serv_got_im(sip->gc, from, html, 0, time(NULL));
2954 g_free(html);
2955 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2956 found = TRUE;
2958 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2959 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2960 xmlnode *state;
2961 gchar *statedata;
2963 if (!isc) {
2964 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2965 return;
2968 state = xmlnode_get_child(isc, "state");
2970 if (!state) {
2971 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2972 xmlnode_free(isc);
2973 return;
2976 statedata = xmlnode_get_data(state);
2977 if (statedata) {
2978 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2979 else serv_got_typing_stopped(sip->gc, from);
2981 g_free(statedata);
2983 xmlnode_free(isc);
2984 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2985 found = TRUE;
2987 if (!found) {
2988 purple_debug_info("sipe", "got unknown mime-type");
2989 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2991 g_free(from);
2994 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
2996 gchar *ms_text_format;
2997 gchar *from;
2998 gchar *body;
2999 struct sip_im_session *session;
3001 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3003 // Only accept text invitations
3004 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3005 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3006 return;
3009 from = parse_from(sipmsg_find_header(msg, "From"));
3010 session = find_or_create_im_session (sip, from);
3011 if (session) {
3012 if (session->dialog) {
3013 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3014 } else {
3015 session->dialog = g_new0(struct sip_dialog, 1);
3017 sipe_parse_dialog(msg, session->dialog, FALSE);
3019 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3020 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3021 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3022 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3024 } else {
3025 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3028 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3029 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3030 if (ms_text_format) {
3031 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3033 gchar *html = get_html_message(ms_text_format, NULL);
3034 if (html) {
3035 serv_got_im(sip->gc, from, html, 0, time(NULL));
3036 g_free(html);
3037 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3041 g_free(from);
3043 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3044 sipmsg_remove_header(msg, "Ms-Text-Format");
3045 sipmsg_remove_header(msg, "EndPoints");
3046 sipmsg_remove_header(msg, "User-Agent");
3047 sipmsg_remove_header(msg, "Roster-Manager");
3049 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3050 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3052 body = g_strdup_printf(
3053 "v=0\r\n"
3054 "o=- 0 0 IN IP4 %s\r\n"
3055 "s=session\r\n"
3056 "c=IN IP4 %s\r\n"
3057 "t=0 0\r\n"
3058 "m=message %d sip sip:%s\r\n"
3059 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3060 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3061 sip->realport, sip->username);
3062 send_sip_response(sip->gc, msg, 200, "OK", body);
3063 g_free(body);
3066 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3068 gchar *body;
3070 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3071 sipmsg_remove_header(msg, "EndPoints");
3072 sipmsg_remove_header(msg, "User-Agent");
3074 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3075 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3077 body = g_strdup_printf(
3078 "v=0\r\n"
3079 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3080 "s=session\r\n"
3081 "c=IN IP4 0.0.0.0\r\n"
3082 "t=0 0\r\n"
3083 "m=message %d sip sip:%s\r\n"
3084 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3085 sip->realport, sip->username);
3086 send_sip_response(sip->gc, msg, 200, "OK", body);
3087 g_free(body);
3090 static void sipe_connection_cleanup(struct sipe_account_data *);
3091 static void create_connection(struct sipe_account_data *, gchar *, int);
3093 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3095 gchar *tmp;
3096 //gchar krb5_token;
3097 const gchar *expires_header;
3098 int expires, i;
3099 GSList *hdr = msg->headers;
3100 GSList *entry;
3101 struct siphdrelement *elem;
3103 expires_header = sipmsg_find_header(msg, "Expires");
3104 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3105 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3107 switch (msg->response) {
3108 case 200:
3109 if (expires == 0) {
3110 sip->registerstatus = 0;
3111 } else {
3112 gchar *contact_hdr = NULL;
3113 gchar *gruu = NULL;
3114 gchar *epid;
3115 gchar *uuid;
3117 if (!sip->reregister_set) {
3118 gchar *action_name = g_strdup_printf("<%s>", "registration");
3119 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
3120 g_free(action_name);
3121 sip->reregister_set = TRUE;
3124 sip->registerstatus = 3;
3126 if (!sip->reauthenticate_set) {
3127 /* we have to reauthenticate as our security token expires
3128 after eight hours (be five minutes early) */
3129 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3130 guint reauth_timeout = (8 * 3600) - 360;
3131 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3132 g_free(action_name);
3133 sip->reauthenticate_set = TRUE;
3136 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3138 epid = get_epid(sip);
3139 uuid = generateUUIDfromEPID(epid);
3140 g_free(epid);
3142 // There can be multiple Contact headers (one per location where the user is logged in) so
3143 // make sure to only get the one for this uuid
3144 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3145 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3146 if (valid_contact) {
3147 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3148 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3149 g_free(valid_contact);
3150 break;
3151 } else {
3152 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3155 g_free(uuid);
3157 g_free(sip->contact);
3158 if(gruu) {
3159 sip->contact = g_strdup_printf("<%s>", gruu);
3160 g_free(gruu);
3161 } else {
3162 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3163 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);
3165 sip->msrtc_event_categories = FALSE;
3167 while(hdr)
3169 elem = hdr->data;
3170 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3171 if (strstr(elem->value, "msrtc-event-categories")) {
3172 sip->msrtc_event_categories = TRUE;
3174 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s, %d\n", elem->value, sip->msrtc_event_categories);
3176 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3177 gchar **caps = g_strsplit(elem->value,",",0);
3178 i = 0;
3179 while (caps[i]) {
3180 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3181 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3182 i++;
3184 g_strfreev(caps);
3186 hdr = g_slist_next(hdr);
3189 if (!sip->subscribed) { //do it just once, not every re-register
3190 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3191 sipe_options_request(sip, sip->sipdomain);
3193 entry = sip->allow_events;
3194 while (entry) {
3195 tmp = entry->data;
3196 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3197 sipe_subscribe_roaming_contacts(sip, msg);
3199 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3200 sipe_subscribe_roaming_acl(sip, msg);
3202 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3203 sipe_subscribe_roaming_self(sip, msg);
3205 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3206 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3207 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3208 sipe_subscribe_roaming_provisioning(sip, msg);
3210 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3211 sipe_subscribe_presence_wpending(sip, msg);
3213 entry = entry->next;
3215 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3216 sip->subscribed = TRUE;
3219 if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
3220 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
3221 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
3222 } else {
3223 tmp = sipmsg_find_header(msg, "ms-keep-alive");
3224 if (tmp) {
3225 sipe_keep_alive_timeout(sip, tmp);
3229 // Should we remove the transaction here?
3230 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3231 transactions_remove(sip, tc);
3233 break;
3234 case 301:
3236 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3238 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3239 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3240 gchar **tmp;
3241 gchar *hostname;
3242 int port = 0;
3243 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3244 int i = 1;
3246 tmp = g_strsplit(parts[0], ":", 0);
3247 hostname = g_strdup(tmp[0]);
3248 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3249 g_strfreev(tmp);
3251 while (parts[i]) {
3252 tmp = g_strsplit(parts[i], "=", 0);
3253 if (tmp[1]) {
3254 if (g_strcasecmp("transport", tmp[0]) == 0) {
3255 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3256 transport = SIPE_TRANSPORT_TCP;
3257 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3258 transport = SIPE_TRANSPORT_UDP;
3262 g_strfreev(tmp);
3263 i++;
3265 g_strfreev(parts);
3267 /* Close old connection */
3268 sipe_connection_cleanup(sip);
3270 /* Create new connection */
3271 sip->transport = transport;
3272 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3273 hostname, port, TRANSPORT_DESCRIPTOR);
3274 create_connection(sip, hostname, port);
3276 g_free(redirect);
3278 break;
3279 case 401:
3280 if (sip->registerstatus != 2) {
3281 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3282 if (sip->registrar.retries > 3) {
3283 sip->gc->wants_to_die = TRUE;
3284 purple_connection_error(sip->gc, _("Wrong Password"));
3285 return TRUE;
3287 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3288 tmp = sipmsg_find_auth_header(msg, "NTLM");
3289 } else {
3290 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3292 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3293 fill_auth(sip, tmp, &sip->registrar);
3294 sip->registerstatus = 2;
3295 if (sip->account->disconnecting) {
3296 do_register_exp(sip, 0);
3297 } else {
3298 do_register(sip);
3301 break;
3302 case 403:
3304 gchar *warning = sipmsg_find_header(msg, "Warning");
3305 if (warning != NULL) {
3306 /* Example header:
3307 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3309 gchar **tmp = g_strsplit(warning, "\"", 0);
3310 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3311 g_strfreev(tmp);
3312 } else {
3313 warning = g_strdup(_("You have been rejected by the server"));
3316 sip->gc->wants_to_die = TRUE;
3317 purple_connection_error(sip->gc, warning);
3318 g_free(warning);
3319 return TRUE;
3321 break;
3322 case 404:
3324 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3325 if (warning != NULL) {
3326 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3327 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3328 g_free(reason);
3329 } else {
3330 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3333 sip->gc->wants_to_die = TRUE;
3334 purple_connection_error(sip->gc, warning);
3335 g_free(warning);
3336 return TRUE;
3338 break;
3339 case 503:
3341 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3342 if (warning != NULL) {
3343 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3344 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3345 g_free(reason);
3346 } else {
3347 warning = g_strdup(_("Service unavailable: no reason given"));
3350 sip->gc->wants_to_die = TRUE;
3351 purple_connection_error(sip->gc, warning);
3352 g_free(warning);
3353 return TRUE;
3355 break;
3357 return TRUE;
3360 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3362 const char *uri;
3363 xmlnode *xn_categories;
3364 xmlnode *xn_category;
3365 xmlnode *xn_node;
3366 const char *activity = NULL;
3368 xn_categories = xmlnode_from_str(data, len);
3369 uri = xmlnode_get_attrib(xn_categories, "uri");
3371 for (xn_category = xmlnode_get_child(xn_categories, "category");
3372 xn_category ;
3373 xn_category = xmlnode_get_next_twin(xn_category) )
3375 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3377 if (!strcmp(attrVar, "note"))
3379 char *note;
3380 struct sipe_buddy *sbuddy;
3381 xn_node = xmlnode_get_child(xn_category, "note");
3382 if (!xn_node) continue;
3383 xn_node = xmlnode_get_child(xn_node, "body");
3384 if (!xn_node) continue;
3386 note = xmlnode_get_data(xn_node);
3388 if(uri){
3389 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3391 if (sbuddy && note)
3393 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note);
3394 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3395 sbuddy->annotation = g_strdup(note);
3397 if(note)
3398 g_free(note);
3400 else if(!strcmp(attrVar, "state"))
3402 char *data;
3403 int avail;
3404 xn_node = xmlnode_get_child(xn_category, "state");
3405 if (!xn_node) continue;
3406 xn_node = xmlnode_get_child(xn_node, "availability");
3407 if (!xn_node) continue;
3409 data = xmlnode_get_data(xn_node);
3410 avail = atoi(data);
3411 g_free(data);
3413 if (avail < 3000)
3414 activity = SIPE_STATUS_ID_UNKNOWN;
3415 else if (avail < 4500)
3416 activity = SIPE_STATUS_ID_AVAILABLE;
3417 else if (avail < 6000)
3418 activity = SIPE_STATUS_ID_BRB;
3419 else if (avail < 7500)
3420 activity = SIPE_STATUS_ID_ONPHONE;
3421 else if (avail < 9000)
3422 activity = SIPE_STATUS_ID_BUSY;
3423 else if (avail < 12000)
3424 activity = SIPE_STATUS_ID_DND;
3425 else if (avail < 18000)
3426 activity = SIPE_STATUS_ID_AWAY;
3427 else
3428 activity = SIPE_STATUS_ID_OFFLINE;
3431 if(activity) {
3432 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
3433 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3436 xmlnode_free(xn_categories);
3439 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3441 const char *uri,*state;
3442 xmlnode *xn_list;
3443 xmlnode *xn_resource;
3444 xmlnode *xn_instance;
3446 xn_list = xmlnode_from_str(data, len);
3448 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3449 xn_resource;
3450 xn_resource = xmlnode_get_next_twin(xn_resource) )
3452 uri = xmlnode_get_attrib(xn_resource, "uri");
3453 xn_instance = xmlnode_get_child(xn_resource, "instance");
3454 if (!xn_instance) return;
3456 state = xmlnode_get_attrib(xn_instance, "state");
3457 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3458 if(strstr(state,"resubscribe")){
3459 sipe_subscribe_presence_single(sip, uri);
3464 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3466 const gchar *uri;
3467 gchar *getbasic;
3468 gchar *activity = NULL;
3469 xmlnode *pidf;
3470 xmlnode *basicstatus = NULL, *tuple, *status;
3471 gboolean isonline = FALSE;
3472 xmlnode *display_name_node;
3474 pidf = xmlnode_from_str(data, len);
3475 if (!pidf) {
3476 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3477 return;
3480 uri = xmlnode_get_attrib(pidf, "entity");
3482 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3484 if ((status = xmlnode_get_child(tuple, "status"))) {
3485 basicstatus = xmlnode_get_child(status, "basic");
3489 if (!basicstatus) {
3490 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3491 xmlnode_free(pidf);
3492 return;
3495 getbasic = xmlnode_get_data(basicstatus);
3496 if (!getbasic) {
3497 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3498 xmlnode_free(pidf);
3499 return;
3502 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3503 if (strstr(getbasic, "open")) {
3504 isonline = TRUE;
3506 g_free(getbasic);
3508 display_name_node = xmlnode_get_child(pidf, "display-name");
3509 // updating display name if alias was just URI
3510 if (display_name_node) {
3511 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3512 GSList *entry = buddies;
3513 PurpleBuddy *p_buddy;
3514 char * display_name = xmlnode_get_data(display_name_node);
3516 while (entry) {
3517 const char *server_alias;
3518 char *alias;
3520 p_buddy = entry->data;
3522 alias = (char *)purple_buddy_get_alias(p_buddy);
3523 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3524 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3525 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3526 purple_blist_alias_buddy(p_buddy, display_name);
3528 g_free(alias);
3530 server_alias = purple_buddy_get_server_alias(p_buddy);
3531 if (display_name &&
3532 ( (server_alias && strcmp(display_name, server_alias))
3533 || !server_alias || strlen(server_alias) == 0 )
3535 purple_blist_server_alias_buddy(p_buddy, display_name);
3538 entry = entry->next;
3540 g_free(display_name);
3543 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3544 if ((status = xmlnode_get_child(tuple, "status"))) {
3545 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3546 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3547 activity = xmlnode_get_data(basicstatus);
3548 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3554 if (isonline) {
3555 const gchar * status_id = NULL;
3556 if (activity) {
3557 if (strstr(activity, "busy")) {
3558 status_id = SIPE_STATUS_ID_BUSY;
3559 } else if (strstr(activity, "away")) {
3560 status_id = SIPE_STATUS_ID_AWAY;
3564 if (!status_id) {
3565 status_id = SIPE_STATUS_ID_AVAILABLE;
3568 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3569 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3570 } else {
3571 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
3574 g_free(activity);
3575 xmlnode_free(pidf);
3578 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3580 const char *availability;
3581 const char *activity;
3582 const char *display_name = NULL;
3583 const char *activity_name;
3584 const char *name;
3585 char *uri;
3586 int avl;
3587 int act;
3588 struct sipe_buddy *sbuddy;
3590 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3592 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3593 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3594 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3595 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3596 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3597 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3598 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3599 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3600 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3601 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3602 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3603 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3605 name = xmlnode_get_attrib(xn_presentity, "uri");
3606 uri = g_strdup_printf("sip:%s", name);
3607 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3608 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3610 // updating display name if alias was just URI
3611 if (xn_display_name) {
3612 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3613 GSList *entry = buddies;
3614 PurpleBuddy *p_buddy;
3615 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3617 while (entry) {
3618 const char *email_str, *server_alias;
3620 p_buddy = entry->data;
3622 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3623 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3624 purple_blist_alias_buddy(p_buddy, display_name);
3627 server_alias = purple_buddy_get_server_alias(p_buddy);
3628 if (display_name &&
3629 ( (server_alias && strcmp(display_name, server_alias))
3630 || !server_alias || strlen(server_alias) == 0 )
3632 purple_blist_server_alias_buddy(p_buddy, display_name);
3635 if (email) {
3636 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3637 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3638 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3642 entry = entry->next;
3646 avl = atoi(availability);
3647 act = atoi(activity);
3649 if (act <= 100)
3650 activity_name = SIPE_STATUS_ID_AWAY;
3651 else if (act <= 150)
3652 activity_name = SIPE_STATUS_ID_LUNCH;
3653 else if (act <= 300)
3654 activity_name = SIPE_STATUS_ID_BRB;
3655 else if (act <= 400)
3656 activity_name = SIPE_STATUS_ID_AVAILABLE;
3657 else if (act <= 500)
3658 activity_name = SIPE_STATUS_ID_ONPHONE;
3659 else if (act <= 600)
3660 activity_name = SIPE_STATUS_ID_BUSY;
3661 else
3662 activity_name = SIPE_STATUS_ID_AVAILABLE;
3664 if (avl == 0)
3665 activity_name = SIPE_STATUS_ID_OFFLINE;
3667 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3668 if (sbuddy)
3670 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3671 sbuddy->annotation = NULL;
3672 if (note) { sbuddy->annotation = g_strdup(note); }
3674 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3675 sbuddy->device_name = NULL;
3676 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3679 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3680 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3681 g_free(note);
3682 xmlnode_free(xn_presentity);
3683 g_free(uri);
3686 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3688 char *ctype = sipmsg_find_header(msg, "Content-Type");
3690 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3692 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3693 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3695 const char *content = msg->body;
3696 unsigned length = msg->bodylen;
3697 PurpleMimeDocument *mime = NULL;
3699 if (strstr(ctype, "multipart"))
3701 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3702 const char *content_type;
3703 GList* parts;
3704 mime = purple_mime_document_parse(doc);
3705 parts = purple_mime_document_get_parts(mime);
3706 while(parts) {
3707 content = purple_mime_part_get_data(parts->data);
3708 length = purple_mime_part_get_length(parts->data);
3709 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3710 if(content_type && strstr(content_type,"application/rlmi+xml"))
3712 process_incoming_notify_rlmi_resub(sip, content, length);
3714 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3716 process_incoming_notify_msrtc(sip, content, length);
3718 else
3720 process_incoming_notify_rlmi(sip, content, length);
3722 parts = parts->next;
3724 g_free(doc);
3726 if (mime)
3728 purple_mime_document_free(mime);
3731 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3733 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3735 else if(strstr(ctype, "application/rlmi+xml"))
3737 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3740 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3742 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3744 else
3746 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3751 * Dispatcher for all incoming subscription information
3752 * whether it comes from NOTIFY, BENOTIFY requests or
3753 * piggy-backed to subscription's OK responce.
3755 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3756 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3758 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3760 gchar *event = sipmsg_find_header(msg, "Event");
3761 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3762 int timeout = 0;
3764 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3765 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3767 if (!request)
3769 const gchar *expires_header;
3770 expires_header = sipmsg_find_header(msg, "Expires");
3771 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3772 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", timeout);
3773 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
3776 if (!subscription_state || strstr(subscription_state, "active"))
3778 if (event && !g_ascii_strcasecmp(event, "presence"))
3780 sipe_process_presence(sip, msg);
3782 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3784 sipe_process_roaming_contacts(sip, msg, NULL);
3786 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
3788 sipe_process_roaming_self(sip, msg);
3790 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3792 sipe_process_roaming_acl(sip, msg);
3794 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3796 sipe_process_presence_wpending(sip, msg);
3798 else
3800 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3804 //The server sends a (BE)NOTIFY with the status 'terminated'
3805 if(request && subscription_state && strstr(subscription_state, "terminated") ) {
3806 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3807 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3808 g_free(from);
3811 if (timeout) {
3812 // For LSC 2005
3813 if (event && !sip->msrtc_event_categories)
3815 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
3816 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
3818 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
3819 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_roaming_contacts, sip, msg);
3820 g_free(action_name);
3822 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
3823 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
3825 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
3826 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_roaming_acl, sip, msg);
3827 g_free(action_name);
3830 else */if (!g_ascii_strcasecmp(event, "presence.wpending") &&
3831 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3833 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3834 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_wpending, sip, NULL);
3835 g_free(action_name);
3837 else if (!g_ascii_strcasecmp(event, "presence") &&
3838 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3840 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3841 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", who);
3842 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_batched, sip, who);
3843 g_free(action_name);
3844 g_free(who);
3849 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
3851 sipe_process_registration_notify(sip, msg);
3854 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3855 if (request && !benotify)
3857 sipmsg_remove_header(msg, "Expires");
3858 sipmsg_remove_header(msg, "subscription-state");
3859 sipmsg_remove_header(msg, "Event");
3860 sipmsg_remove_header(msg, "Require");
3861 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3866 * unused. Needed?
3868 static gchar* gen_xpidf(struct sipe_account_data *sip)
3870 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3871 "<presence>\r\n"
3872 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3873 "<display name=\"sip:%s\"/>\r\n"
3874 "<atom id=\"1234\">\r\n"
3875 "<address uri=\"sip:%s\">\r\n"
3876 "<status status=\"%s\"/>\r\n"
3877 "</address>\r\n"
3878 "</atom>\r\n"
3879 "</presence>\r\n",
3880 sip->username,
3881 sip->username,
3882 sip->username,
3883 sip->status);
3884 return doc;
3889 static gchar* gen_pidf(struct sipe_account_data *sip)
3891 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3892 "<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"
3893 "<tuple id=\"0\">\r\n"
3894 "<status>\r\n"
3895 "<basic>open</basic>\r\n"
3896 "<ep:activities>\r\n"
3897 " <ep:activity>%s</ep:activity>\r\n"
3898 "</ep:activities>"
3899 "</status>\r\n"
3900 "</tuple>\r\n"
3901 "<ci:display-name>%s</ci:display-name>\r\n"
3902 "</presence>",
3903 sip->username,
3904 sip->status,
3905 sip->username);
3906 return doc;
3910 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
3912 int availability = 300; // online
3913 int activity = 400; // Available
3914 gchar *name;
3915 gchar *body;
3916 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
3917 activity = 100;
3918 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
3919 activity = 150;
3920 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
3921 activity = 300;
3922 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
3923 activity = 400;
3924 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
3925 activity = 500;
3926 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
3927 activity = 600;
3928 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
3929 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
3930 availability = 0; // offline
3931 activity = 100;
3932 } else {
3933 activity = 400; // available
3936 name = g_strdup_printf("sip: sip:%s", sip->username);
3937 //@TODO: send user data - state; add hostname in upper case
3938 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3939 send_soap_request_with_cb(sip, body, NULL , NULL);
3940 g_free(name);
3941 g_free(body);
3944 static gboolean
3945 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3947 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3948 if (msg->response == 200) {
3949 sip->status_version = 0;
3950 send_presence_status(sip);
3952 return TRUE;
3955 static gboolean
3956 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3958 if (msg->response == 409) {
3959 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3960 // TODO need to parse the version #'s?
3961 gchar *uri = g_strdup_printf("sip:%s", sip->username);
3962 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
3963 gchar *tmp;
3964 gchar *hdr;
3966 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
3968 tmp = get_contact(sip);
3969 hdr = g_strdup_printf("Contact: %s\r\n"
3970 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
3972 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
3974 g_free(tmp);
3975 g_free(hdr);
3976 g_free(uri);
3977 g_free(doc);
3979 return TRUE;
3982 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
3984 int code;
3985 gchar *uri;
3986 gchar *doc;
3987 gchar *tmp;
3988 gchar *hdr;
3989 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
3990 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
3991 code = 12000;
3992 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
3993 code = 9000;
3994 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
3995 code = 7500;
3996 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
3997 code = 6000;
3998 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
3999 code = 4500;
4000 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4001 code = 3000;
4002 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4003 code = 0;
4004 } else {
4005 // Offline or invisible
4006 code = 18000;
4009 uri = g_strdup_printf("sip:%s", sip->username);
4010 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4011 sip->status_version, code,
4012 sip->status_version, code,
4013 sip->status_version, note ? note : "",
4014 sip->status_version, note ? note : "",
4015 sip->status_version, note ? note : ""
4017 sip->status_version++;
4019 tmp = get_contact(sip);
4020 hdr = g_strdup_printf("Contact: %s\r\n"
4021 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4023 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4025 g_free(tmp);
4026 g_free(hdr);
4027 g_free(uri);
4028 g_free(doc);
4031 static void send_presence_status(struct sipe_account_data *sip)
4033 PurpleStatus * status = purple_account_get_active_status(sip->account);
4034 const gchar *note;
4035 if (!status) return;
4037 note = purple_status_get_attr_string(status, "message");
4039 if(sip->msrtc_event_categories){
4040 send_presence_category_publish(sip, note);
4041 } else {
4042 send_presence_soap(sip, note);
4046 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4048 gboolean found = FALSE;
4049 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4050 if (msg->response == 0) { /* request */
4051 if (!strcmp(msg->method, "MESSAGE")) {
4052 process_incoming_message(sip, msg);
4053 found = TRUE;
4054 } else if (!strcmp(msg->method, "NOTIFY")) {
4055 purple_debug_info("sipe","send->process_incoming_notify\n");
4056 process_incoming_notify(sip, msg, TRUE, FALSE);
4057 found = TRUE;
4058 } else if (!strcmp(msg->method, "BENOTIFY")) {
4059 purple_debug_info("sipe","send->process_incoming_benotify\n");
4060 process_incoming_notify(sip, msg, TRUE, TRUE);
4061 found = TRUE;
4062 } else if (!strcmp(msg->method, "INVITE")) {
4063 process_incoming_invite(sip, msg);
4064 found = TRUE;
4065 } else if (!strcmp(msg->method, "OPTIONS")) {
4066 process_incoming_options(sip, msg);
4067 found = TRUE;
4068 } else if (!strcmp(msg->method, "INFO")) {
4069 // TODO needs work
4070 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4071 if (from) {
4072 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4074 g_free(from);
4075 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4076 found = TRUE;
4077 } else if (!strcmp(msg->method, "ACK")) {
4078 // ACK's don't need any response
4079 found = TRUE;
4080 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4081 // LCS 2005 sends us these - just respond 200 OK
4082 found = TRUE;
4083 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4084 } else if (!strcmp(msg->method, "BYE")) {
4085 struct sip_im_session *session;
4086 gchar *from;
4087 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4089 from = parse_from(sipmsg_find_header(msg, "From"));
4090 session = find_im_session (sip, from);
4091 g_free(from);
4093 if (session) {
4094 // TODO Let the user know the other user left the conversation?
4095 im_session_destroy(sip, session);
4098 found = TRUE;
4099 } else {
4100 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4102 } else { /* response */
4103 struct transaction *trans = transactions_find(sip, msg);
4104 if (trans) {
4105 if (msg->response == 407) {
4106 gchar *resend, *auth, *ptmp;
4108 if (sip->proxy.retries > 30) return;
4109 sip->proxy.retries++;
4110 /* do proxy authentication */
4112 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4114 fill_auth(sip, ptmp, &sip->proxy);
4115 auth = auth_header(sip, &sip->proxy, trans->msg);
4116 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4117 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4118 g_free(auth);
4119 resend = sipmsg_to_string(trans->msg);
4120 /* resend request */
4121 sendout_pkt(sip->gc, resend);
4122 g_free(resend);
4123 } else {
4124 if (msg->response == 100 || msg->response == 180) {
4125 /* ignore provisional response */
4126 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4127 } else {
4128 sip->proxy.retries = 0;
4129 if (!strcmp(trans->msg->method, "REGISTER")) {
4130 if (msg->response == 401)
4132 sip->registrar.retries++;
4133 sip->registrar.expires = 0;
4135 else
4137 sip->registrar.retries = 0;
4139 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4140 } else {
4141 if (msg->response == 401) {
4142 gchar *resend, *auth, *ptmp;
4144 if (sip->registrar.retries > 4) return;
4145 sip->registrar.retries++;
4147 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4148 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4149 } else {
4150 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4153 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4155 fill_auth(sip, ptmp, &sip->registrar);
4156 auth = auth_header(sip, &sip->registrar, trans->msg);
4157 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4158 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
4160 //sipmsg_remove_header(trans->msg, "Authorization");
4161 //sipmsg_add_header(trans->msg, "Authorization", auth);
4162 g_free(auth);
4163 resend = sipmsg_to_string(trans->msg);
4164 /* resend request */
4165 sendout_pkt(sip->gc, resend);
4166 g_free(resend);
4170 if (trans->callback) {
4171 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4172 /* call the callback to process response*/
4173 (trans->callback)(sip, msg, trans);
4175 /* Not sure if this is needed or what needs to be done
4176 but transactions seem to be removed prematurely so
4177 this only removes them if the response is 200 OK */
4178 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4179 /*Has a bug and it's unneccesary*/
4180 /*transactions_remove(sip, trans);*/
4184 found = TRUE;
4185 } else {
4186 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4189 if (!found) {
4190 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4194 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4196 char *cur;
4197 char *dummy;
4198 struct sipmsg *msg;
4199 int restlen;
4200 cur = conn->inbuf;
4202 /* according to the RFC remove CRLF at the beginning */
4203 while (*cur == '\r' || *cur == '\n') {
4204 cur++;
4206 if (cur != conn->inbuf) {
4207 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4208 conn->inbufused = strlen(conn->inbuf);
4211 /* Received a full Header? */
4212 sip->processing_input = TRUE;
4213 while (sip->processing_input &&
4214 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4215 time_t currtime = time(NULL);
4216 cur += 2;
4217 cur[0] = '\0';
4218 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4219 msg = sipmsg_parse_header(conn->inbuf);
4220 cur[0] = '\r';
4221 cur += 2;
4222 restlen = conn->inbufused - (cur - conn->inbuf);
4223 if (restlen >= msg->bodylen) {
4224 dummy = g_malloc(msg->bodylen + 1);
4225 memcpy(dummy, cur, msg->bodylen);
4226 dummy[msg->bodylen] = '\0';
4227 msg->body = dummy;
4228 cur += msg->bodylen;
4229 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4230 conn->inbufused = strlen(conn->inbuf);
4231 } else {
4232 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4233 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4234 sipmsg_free(msg);
4235 return;
4238 /*if (msg->body) {
4239 purple_debug_info("sipe", "body:\n%s", msg->body);
4242 // Verify the signature before processing it
4243 if (sip->registrar.ntlm_key) {
4244 struct sipmsg_breakdown msgbd;
4245 gchar *signature_input_str;
4246 gchar *signature = NULL;
4247 gchar *rspauth;
4248 msgbd.msg = msg;
4249 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4250 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4251 if (signature_input_str != NULL) {
4252 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4254 g_free(signature_input_str);
4256 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4258 if (signature != NULL) {
4259 if (rspauth != NULL) {
4260 if (purple_ntlm_verify_signature (signature, rspauth)) {
4261 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4262 process_input_message(sip, msg);
4263 } else {
4264 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4265 purple_connection_error(sip->gc, _("Invalid message signature received"));
4266 sip->gc->wants_to_die = TRUE;
4268 } else if (msg->response == 401) {
4269 purple_connection_error(sip->gc, _("Wrong Password"));
4270 sip->gc->wants_to_die = TRUE;
4272 g_free(signature);
4275 g_free(rspauth);
4276 sipmsg_breakdown_free(&msgbd);
4277 } else {
4278 process_input_message(sip, msg);
4281 sipmsg_free(msg);
4285 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4287 PurpleConnection *gc = data;
4288 struct sipe_account_data *sip = gc->proto_data;
4289 struct sipmsg *msg;
4290 int len;
4291 time_t currtime;
4293 static char buffer[65536];
4294 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4295 buffer[len] = '\0';
4296 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4297 msg = sipmsg_parse_msg(buffer);
4298 if (msg) process_input_message(sip, msg);
4302 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4304 struct sipe_account_data *sip = gc->proto_data;
4305 PurpleSslConnection *gsc = sip->gsc;
4307 purple_debug_error("sipe", "%s",debug);
4308 purple_connection_error(gc, msg);
4310 /* Invalidate this connection. Next send will open a new one */
4311 if (gsc) {
4312 connection_remove(sip, gsc->fd);
4313 purple_ssl_close(gsc);
4315 sip->gsc = NULL;
4316 sip->fd = -1;
4319 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4321 PurpleConnection *gc = data;
4322 struct sipe_account_data *sip;
4323 struct sip_connection *conn;
4324 int readlen, len;
4325 gboolean firstread = TRUE;
4327 /* NOTE: This check *IS* necessary */
4328 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4329 purple_ssl_close(gsc);
4330 return;
4333 sip = gc->proto_data;
4334 conn = connection_find(sip, gsc->fd);
4335 if (conn == NULL) {
4336 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4337 gc->wants_to_die = TRUE;
4338 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4339 return;
4342 /* Read all available data from the SSL connection */
4343 do {
4344 /* Increase input buffer size as needed */
4345 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4346 conn->inbuflen += SIMPLE_BUF_INC;
4347 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4348 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4351 /* Try to read as much as there is space left in the buffer */
4352 readlen = conn->inbuflen - conn->inbufused - 1;
4353 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4355 if (len < 0 && errno == EAGAIN) {
4356 /* Try again later */
4357 return;
4358 } else if (len < 0) {
4359 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4360 return;
4361 } else if (firstread && (len == 0)) {
4362 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4363 return;
4366 conn->inbufused += len;
4367 firstread = FALSE;
4369 /* Equivalence indicates that there is possibly more data to read */
4370 } while (len == readlen);
4372 conn->inbuf[conn->inbufused] = '\0';
4373 process_input(sip, conn);
4377 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4379 PurpleConnection *gc = data;
4380 struct sipe_account_data *sip = gc->proto_data;
4381 int len;
4382 struct sip_connection *conn = connection_find(sip, source);
4383 if (!conn) {
4384 purple_debug_error("sipe", "Connection not found!\n");
4385 return;
4388 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4389 conn->inbuflen += SIMPLE_BUF_INC;
4390 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4393 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4395 if (len < 0 && errno == EAGAIN)
4396 return;
4397 else if (len <= 0) {
4398 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4399 connection_remove(sip, source);
4400 if (sip->fd == source) sip->fd = -1;
4401 return;
4404 conn->inbufused += len;
4405 conn->inbuf[conn->inbufused] = '\0';
4407 process_input(sip, conn);
4410 /* Callback for new connections on incoming TCP port */
4411 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4413 PurpleConnection *gc = data;
4414 struct sipe_account_data *sip = gc->proto_data;
4415 struct sip_connection *conn;
4417 int newfd = accept(source, NULL, NULL);
4419 conn = connection_create(sip, newfd);
4421 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4424 static void login_cb(gpointer data, gint source, const gchar *error_message)
4426 PurpleConnection *gc = data;
4427 struct sipe_account_data *sip;
4428 struct sip_connection *conn;
4430 if (!PURPLE_CONNECTION_IS_VALID(gc))
4432 if (source >= 0)
4433 close(source);
4434 return;
4437 if (source < 0) {
4438 purple_connection_error(gc, _("Could not connect"));
4439 return;
4442 sip = gc->proto_data;
4443 sip->fd = source;
4444 sip->last_keepalive = time(NULL);
4446 conn = connection_create(sip, source);
4448 do_register(sip);
4450 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4453 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4455 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4456 if (sip == NULL) return;
4458 do_register(sip);
4461 static guint sipe_ht_hash_nick(const char *nick)
4463 char *lc = g_utf8_strdown(nick, -1);
4464 guint bucket = g_str_hash(lc);
4465 g_free(lc);
4467 return bucket;
4470 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4472 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4475 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4477 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4479 sip->listen_data = NULL;
4481 if (listenfd == -1) {
4482 purple_connection_error(sip->gc, _("Could not create listen socket"));
4483 return;
4486 sip->fd = listenfd;
4488 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4489 sip->listenfd = sip->fd;
4491 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4493 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4494 do_register(sip);
4497 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4499 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4500 int addr_size;
4502 sip->query_data = NULL;
4504 if (!hosts || !hosts->data) {
4505 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4506 return;
4509 addr_size = GPOINTER_TO_INT(hosts->data);
4510 hosts = g_slist_remove(hosts, hosts->data);
4511 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4512 g_free(hosts->data);
4513 hosts = g_slist_remove(hosts, hosts->data);
4514 while (hosts) {
4515 hosts = g_slist_remove(hosts, hosts->data);
4516 g_free(hosts->data);
4517 hosts = g_slist_remove(hosts, hosts->data);
4520 /* create socket for incoming connections */
4521 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4522 sipe_udp_host_resolved_listen_cb, sip);
4523 if (sip->listen_data == NULL) {
4524 purple_connection_error(sip->gc, _("Could not create listen socket"));
4525 return;
4529 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4530 gpointer data)
4532 PurpleConnection *gc = data;
4533 struct sipe_account_data *sip;
4535 /* If the connection is already disconnected, we don't need to do anything else */
4536 if (!PURPLE_CONNECTION_IS_VALID(gc))
4537 return;
4539 sip = gc->proto_data;
4540 sip->fd = -1;
4541 sip->gsc = NULL;
4543 switch(error) {
4544 case PURPLE_SSL_CONNECT_FAILED:
4545 purple_connection_error(gc, _("Connection Failed"));
4546 break;
4547 case PURPLE_SSL_HANDSHAKE_FAILED:
4548 purple_connection_error(gc, _("SSL Handshake Failed"));
4549 break;
4550 case PURPLE_SSL_CERTIFICATE_INVALID:
4551 purple_connection_error(gc, _("SSL Certificate Invalid"));
4552 break;
4556 static void
4557 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4559 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4560 PurpleProxyConnectData *connect_data;
4562 sip->listen_data = NULL;
4564 sip->listenfd = listenfd;
4565 if (sip->listenfd == -1) {
4566 purple_connection_error(sip->gc, _("Could not create listen socket"));
4567 return;
4570 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4571 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4572 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4573 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4574 sipe_newconn_cb, sip->gc);
4575 purple_debug_info("sipe", "connecting to %s port %d\n",
4576 sip->realhostname, sip->realport);
4577 /* open tcp connection to the server */
4578 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4579 sip->realport, login_cb, sip->gc);
4581 if (connect_data == NULL) {
4582 purple_connection_error(sip->gc, _("Couldn't create socket"));
4587 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4589 PurpleAccount *account = sip->account;
4590 PurpleConnection *gc = sip->gc;
4592 if (purple_account_get_bool(account, "useport", FALSE)) {
4593 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4594 port = purple_account_get_int(account, "port", 0);
4595 } else {
4596 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4599 sip->realhostname = hostname;
4600 sip->realport = port;
4602 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4603 hostname, port);
4605 /* TODO: is there a good default grow size? */
4606 if (sip->transport != SIPE_TRANSPORT_UDP)
4607 sip->txbuf = purple_circ_buffer_new(0);
4609 if (sip->transport == SIPE_TRANSPORT_TLS) {
4610 /* SSL case */
4611 if (!purple_ssl_is_supported()) {
4612 gc->wants_to_die = TRUE;
4613 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4614 return;
4617 purple_debug_info("sipe", "using SSL\n");
4619 sip->gsc = purple_ssl_connect(account, hostname, port,
4620 login_cb_ssl, sipe_ssl_connect_failure, gc);
4621 if (sip->gsc == NULL) {
4622 purple_connection_error(gc, _("Could not create SSL context"));
4623 return;
4625 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4626 /* UDP case */
4627 purple_debug_info("sipe", "using UDP\n");
4629 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4630 if (sip->query_data == NULL) {
4631 purple_connection_error(gc, _("Could not resolve hostname"));
4633 } else {
4634 /* TCP case */
4635 purple_debug_info("sipe", "using TCP\n");
4636 /* create socket for incoming connections */
4637 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4638 sipe_tcp_connect_listen_cb, sip);
4639 if (sip->listen_data == NULL) {
4640 purple_connection_error(gc, _("Could not create listen socket"));
4641 return;
4646 /* Service list for autodection */
4647 static const struct sipe_service_data service_autodetect[] = {
4648 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4649 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4650 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4651 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4652 { NULL, NULL, 0 }
4655 /* Service list for SSL/TLS */
4656 static const struct sipe_service_data service_tls[] = {
4657 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4658 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4659 { NULL, NULL, 0 }
4662 /* Service list for TCP */
4663 static const struct sipe_service_data service_tcp[] = {
4664 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4665 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4666 { NULL, NULL, 0 }
4669 /* Service list for UDP */
4670 static const struct sipe_service_data service_udp[] = {
4671 { "sip", "udp", SIPE_TRANSPORT_UDP },
4672 { NULL, NULL, 0 }
4675 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4676 static void resolve_next_service(struct sipe_account_data *sip,
4677 const struct sipe_service_data *start)
4679 if (start) {
4680 sip->service_data = start;
4681 } else {
4682 sip->service_data++;
4683 if (sip->service_data->service == NULL) {
4684 gchar *hostname;
4685 /* Try connecting to the SIP hostname directly */
4686 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4687 if (sip->auto_transport) {
4688 // If SSL is supported, default to using it; OCS servers aren't configured
4689 // by default to accept TCP
4690 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4691 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4692 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4695 hostname = g_strdup(sip->sipdomain);
4696 create_connection(sip, hostname, 0);
4697 return;
4701 /* Try to resolve next service */
4702 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4703 sip->service_data->transport,
4704 sip->sipdomain,
4705 srvresolved, sip);
4708 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4710 struct sipe_account_data *sip = data;
4712 sip->srv_query_data = NULL;
4714 /* find the host to connect to */
4715 if (results) {
4716 gchar *hostname = g_strdup(resp->hostname);
4717 int port = resp->port;
4718 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4719 hostname, port);
4720 g_free(resp);
4722 sip->transport = sip->service_data->type;
4724 create_connection(sip, hostname, port);
4725 } else {
4726 resolve_next_service(sip, NULL);
4730 static void sipe_login(PurpleAccount *account)
4732 PurpleConnection *gc;
4733 struct sipe_account_data *sip;
4734 gchar **signinname_login, **userserver, **domain_user;
4735 const char *transport;
4737 const char *username = purple_account_get_username(account);
4738 gc = purple_account_get_connection(account);
4740 if (strpbrk(username, "\t\v\r\n") != NULL) {
4741 gc->wants_to_die = TRUE;
4742 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
4743 return;
4746 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4747 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4748 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4749 sip->gc = gc;
4750 sip->account = account;
4751 sip->reregister_set = FALSE;
4752 sip->reauthenticate_set = FALSE;
4753 sip->subscribed = FALSE;
4754 sip->subscribed_buddies = FALSE;
4756 signinname_login = g_strsplit(username, ",", 2);
4758 userserver = g_strsplit(signinname_login[0], "@", 2);
4759 purple_connection_set_display_name(gc, userserver[0]);
4760 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4761 sip->sipdomain = g_strdup(userserver[1]);
4763 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
4764 gc->wants_to_die = TRUE;
4765 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4766 return;
4769 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4770 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4771 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4773 sip->password = g_strdup(purple_connection_get_password(gc));
4775 g_strfreev(userserver);
4776 g_strfreev(domain_user);
4777 g_strfreev(signinname_login);
4779 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4781 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4783 /* TODO: Set the status correctly. */
4784 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4786 transport = purple_account_get_string(account, "transport", "auto");
4787 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4788 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4789 SIPE_TRANSPORT_UDP;
4791 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4792 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4793 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4794 } else if (strcmp(transport, "auto") == 0) {
4795 sip->auto_transport = TRUE;
4796 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4797 } else if (strcmp(transport, "tls") == 0) {
4798 resolve_next_service(sip, service_tls);
4799 } else if (strcmp(transport, "tcp") == 0) {
4800 resolve_next_service(sip, service_tcp);
4801 } else {
4802 resolve_next_service(sip, service_udp);
4806 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4808 connection_free_all(sip);
4810 g_free(sip->epid);
4811 sip->epid = NULL;
4813 if (sip->query_data != NULL)
4814 purple_dnsquery_destroy(sip->query_data);
4815 sip->query_data = NULL;
4817 if (sip->srv_query_data != NULL)
4818 purple_srv_cancel(sip->srv_query_data);
4819 sip->srv_query_data = NULL;
4821 if (sip->listen_data != NULL)
4822 purple_network_listen_cancel(sip->listen_data);
4823 sip->listen_data = NULL;
4825 if (sip->gsc != NULL)
4826 purple_ssl_close(sip->gsc);
4827 sip->gsc = NULL;
4829 sipe_auth_free(&sip->registrar);
4830 sipe_auth_free(&sip->proxy);
4832 if (sip->txbuf)
4833 purple_circ_buffer_destroy(sip->txbuf);
4834 sip->txbuf = NULL;
4836 g_free(sip->realhostname);
4837 sip->realhostname = NULL;
4839 if (sip->listenpa)
4840 purple_input_remove(sip->listenpa);
4841 sip->listenpa = 0;
4842 if (sip->tx_handler)
4843 purple_input_remove(sip->tx_handler);
4844 sip->tx_handler = 0;
4845 if (sip->resendtimeout)
4846 purple_timeout_remove(sip->resendtimeout);
4847 sip->resendtimeout = 0;
4848 if (sip->timeouts) {
4849 GSList *entry = sip->timeouts;
4850 while (entry) {
4851 struct scheduled_action *sched_action = entry->data;
4852 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4853 purple_timeout_remove(sched_action->timeout_handler);
4854 g_free(sched_action->payload);
4855 g_free(sched_action->name);
4856 g_free(sched_action);
4857 entry = entry->next;
4860 g_slist_free(sip->timeouts);
4862 if (sip->allow_events) {
4863 GSList *entry = sip->allow_events;
4864 while (entry) {
4865 g_free(entry->data);
4866 entry = entry->next;
4869 g_slist_free(sip->allow_events);
4871 if (sip->contact)
4872 g_free(sip->contact);
4873 sip->contact = NULL;
4874 if (sip->regcallid)
4875 g_free(sip->regcallid);
4876 sip->regcallid = NULL;
4878 sip->fd = -1;
4879 sip->processing_input = FALSE;
4883 * A callback for g_hash_table_foreach_remove
4885 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4887 sipe_free_buddy(buddy);
4890 static void sipe_close(PurpleConnection *gc)
4892 struct sipe_account_data *sip = gc->proto_data;
4894 if (sip) {
4895 /* leave all conversations */
4896 im_session_close_all(sip);
4898 /* unregister */
4899 do_register_exp(sip, 0);
4901 sipe_connection_cleanup(sip);
4902 g_free(sip->sipdomain);
4903 g_free(sip->username);
4904 g_free(sip->password);
4905 g_free(sip->authdomain);
4906 g_free(sip->authuser);
4907 g_free(sip->status);
4909 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4910 g_hash_table_destroy(sip->buddies);
4912 g_free(gc->proto_data);
4913 gc->proto_data = NULL;
4916 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4918 PurpleAccount *acct = purple_connection_get_account(gc);
4919 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4920 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4921 if (conv == NULL)
4922 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4923 purple_conversation_present(conv);
4924 g_free(id);
4927 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4930 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4931 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4934 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4936 PurpleNotifySearchResults *results;
4937 PurpleNotifySearchColumn *column;
4938 xmlnode *searchResults;
4939 xmlnode *mrow;
4940 int match_count = 0;
4941 gboolean more = FALSE;
4942 gchar *secondary;
4944 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
4946 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4947 if (!searchResults) {
4948 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4949 return FALSE;
4952 results = purple_notify_searchresults_new();
4954 if (results == NULL) {
4955 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4956 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4958 xmlnode_free(searchResults);
4959 return FALSE;
4962 column = purple_notify_searchresults_column_new(_("User Name"));
4963 purple_notify_searchresults_column_add(results, column);
4965 column = purple_notify_searchresults_column_new(_("Name"));
4966 purple_notify_searchresults_column_add(results, column);
4968 column = purple_notify_searchresults_column_new(_("Company"));
4969 purple_notify_searchresults_column_add(results, column);
4971 column = purple_notify_searchresults_column_new(_("Country"));
4972 purple_notify_searchresults_column_add(results, column);
4974 column = purple_notify_searchresults_column_new(_("Email"));
4975 purple_notify_searchresults_column_add(results, column);
4977 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
4978 GList *row = NULL;
4980 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
4981 row = g_list_append(row, g_strdup(uri_parts[1]));
4982 g_strfreev(uri_parts);
4984 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
4985 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
4986 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
4987 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
4989 purple_notify_searchresults_row_add(results, row);
4990 match_count++;
4993 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
4994 char *data = xmlnode_get_data_unescaped(mrow);
4995 more = (g_strcasecmp(data, "true") == 0);
4996 g_free(data);
4999 secondary = g_strdup_printf(
5000 dngettext(GETTEXT_PACKAGE,
5001 "Found %d contact%s:",
5002 "Found %d contacts%s:", match_count),
5003 match_count, more ? _(" (more matched your query)") : "");
5005 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5006 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5007 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5009 g_free(secondary);
5010 xmlnode_free(searchResults);
5011 return TRUE;
5014 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5016 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5017 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5018 unsigned i = 0;
5020 do {
5021 PurpleRequestField *field = entries->data;
5022 const char *id = purple_request_field_get_id(field);
5023 const char *value = purple_request_field_string_get_value(field);
5025 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5027 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5028 } while ((entries = g_list_next(entries)) != NULL);
5029 attrs[i] = NULL;
5031 if (i > 0) {
5032 gchar *query = g_strjoinv(NULL, attrs);
5033 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5034 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5035 send_soap_request_with_cb(gc->proto_data, body,
5036 (TransCallback) process_search_contact_response, NULL);
5037 g_free(body);
5038 g_free(query);
5041 g_strfreev(attrs);
5044 static void sipe_show_find_contact(PurplePluginAction *action)
5046 PurpleConnection *gc = (PurpleConnection *) action->context;
5047 PurpleRequestFields *fields;
5048 PurpleRequestFieldGroup *group;
5049 PurpleRequestField *field;
5051 fields = purple_request_fields_new();
5052 group = purple_request_field_group_new(NULL);
5053 purple_request_fields_add_group(fields, group);
5055 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5056 purple_request_field_group_add_field(group, field);
5057 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5058 purple_request_field_group_add_field(group, field);
5059 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5060 purple_request_field_group_add_field(group, field);
5061 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5062 purple_request_field_group_add_field(group, field);
5064 purple_request_fields(gc,
5065 _("Search"),
5066 _("Search for a Contact"),
5067 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5068 fields,
5069 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5070 _("_Cancel"), NULL,
5071 purple_connection_get_account(gc), NULL, NULL, gc);
5074 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5076 GList *menu = NULL;
5077 PurplePluginAction *act;
5079 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5080 menu = g_list_prepend(menu, act);
5082 menu = g_list_reverse(menu);
5084 return menu;
5087 static void dummy_permit_deny(PurpleConnection *gc)
5091 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5093 return TRUE;
5097 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5099 return TRUE;
5103 static char *sipe_status_text(PurpleBuddy *buddy)
5105 struct sipe_account_data *sip;
5106 struct sipe_buddy *sbuddy;
5107 char *text = NULL;
5109 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5110 if (sip) //happens on pidgin exit
5112 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5113 if (sbuddy && sbuddy->annotation)
5115 text = g_strdup(sbuddy->annotation);
5119 return text;
5122 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5124 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5125 const PurpleStatus *status = purple_presence_get_active_status(presence);
5126 struct sipe_account_data *sip;
5127 struct sipe_buddy *sbuddy;
5128 char *annotation = NULL;
5130 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5131 if (sip) //happens on pidgin exit
5133 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5134 if (sbuddy)
5136 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5140 //Layout
5141 if (purple_presence_is_online(presence))
5143 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5146 if (annotation)
5148 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5149 g_free(annotation);
5154 static GHashTable *
5155 sipe_get_account_text_table(PurpleAccount *account)
5157 GHashTable *table;
5158 table = g_hash_table_new(g_str_hash, g_str_equal);
5159 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5160 return table;
5163 static PurpleBuddy *
5164 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5166 PurpleBuddy *clone;
5167 const gchar *server_alias, *email;
5168 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5170 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5172 purple_blist_add_buddy(clone, NULL, group, NULL);
5174 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5175 if (server_alias) {
5176 purple_blist_server_alias_buddy(clone, server_alias);
5179 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5180 if (email) {
5181 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5184 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5185 //for UI to update;
5186 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5187 return clone;
5190 static void
5191 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5193 PurpleBuddy *buddy, *b;
5194 PurpleConnection *gc;
5195 PurpleGroup * group = purple_find_group(group_name);
5197 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5199 buddy = (PurpleBuddy *)node;
5201 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5202 gc = purple_account_get_connection(buddy->account);
5204 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5205 if (!b){
5206 b = purple_blist_add_buddy_clone(group, buddy);
5209 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5212 static void
5213 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5215 const gchar *email;
5216 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5218 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5219 if (email)
5221 char *mailto = g_strdup_printf("mailto:%s", email);
5222 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5223 #ifndef _WIN32
5225 pid_t pid;
5226 char *const parmList[] = {mailto, NULL};
5227 if ((pid = fork()) == -1)
5229 purple_debug_info("sipe", "fork() error\n");
5231 else if (pid == 0)
5233 execvp("xdg-email", parmList);
5234 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5237 #else
5239 BOOL ret;
5240 _flushall();
5241 errno = 0;
5242 //@TODO resolve env variable %WINDIR% first
5243 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5244 if (errno)
5246 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5249 #endif
5251 g_free(mailto);
5253 else
5255 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5260 * A menu which appear when right-clicking on buddy in contact list.
5262 static GList *
5263 sipe_buddy_menu(PurpleBuddy *buddy)
5265 PurpleBlistNode *g_node;
5266 PurpleGroup *group, *gr_parent;
5267 PurpleMenuAction *act;
5268 GList *menu = NULL;
5269 GList *menu_groups = NULL;
5271 act = purple_menu_action_new(_("Send Email..."),
5272 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5273 NULL, NULL);
5274 menu = g_list_prepend(menu, act);
5276 gr_parent = purple_buddy_get_group(buddy);
5277 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5278 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5279 continue;
5281 group = (PurpleGroup *)g_node;
5282 if (group == gr_parent)
5283 continue;
5285 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5286 continue;
5288 act = purple_menu_action_new(purple_group_get_name(group),
5289 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5290 group->name, NULL);
5291 menu_groups = g_list_prepend(menu_groups, act);
5293 menu_groups = g_list_reverse(menu_groups);
5295 act = purple_menu_action_new(_("Copy to"),
5296 NULL,
5297 NULL, menu_groups);
5298 menu = g_list_prepend(menu, act);
5299 menu = g_list_reverse(menu);
5301 return menu;
5304 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5305 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5306 return sipe_buddy_menu((PurpleBuddy *) node);
5307 } else {
5308 return NULL;
5312 static gboolean
5313 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5315 gboolean ret = TRUE;
5316 char *username = (char *)trans->payload;
5318 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5319 PurpleBuddy *pbuddy;
5320 struct sipe_buddy *sbuddy;
5321 const char *alias;
5322 char *server_alias = NULL;
5323 char *email = NULL;
5324 const char *device_name = NULL;
5326 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5328 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5329 alias = purple_buddy_get_local_alias(pbuddy);
5331 if (sip)
5333 //will query buddy UA's capabilities and send answer to log
5334 sipe_options_request(sip, username);
5336 sbuddy = g_hash_table_lookup(sip->buddies, username);
5337 if (sbuddy)
5339 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5343 if (msg->response != 200) {
5344 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5345 } else {
5346 xmlnode *searchResults;
5347 xmlnode *mrow;
5349 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5350 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5351 if (!searchResults) {
5352 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5353 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5354 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5355 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5356 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5357 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5358 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5359 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5360 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5361 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5362 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5363 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5364 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5365 if (!email || strcmp("", email)) {
5366 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5367 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5371 xmlnode_free(searchResults);
5374 purple_notify_user_info_add_section_break(info);
5376 if (!server_alias || !strcmp("", server_alias)) {
5377 g_free(server_alias);
5378 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5379 if (server_alias) {
5380 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5384 // same as server alias, do not present
5385 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5386 if (alias)
5388 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5391 if (!email || !strcmp("", email)) {
5392 g_free(email);
5393 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5394 if (email) {
5395 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5399 if (device_name)
5401 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5404 /* show a buddy's user info in a nice dialog box */
5405 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5406 username, /* buddy's username */
5407 info, /* body */
5408 NULL, /* callback called when dialog closed */
5409 NULL); /* userdata for callback */
5411 return ret;
5415 * AD search first, LDAP based
5417 static void sipe_get_info(PurpleConnection *gc, const char *username)
5419 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5420 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5422 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5423 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5424 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5425 g_free(body);
5426 g_free(row);
5429 static PurplePlugin *my_protocol = NULL;
5431 static PurplePluginProtocolInfo prpl_info =
5434 NULL, /* user_splits */
5435 NULL, /* protocol_options */
5436 NO_BUDDY_ICONS, /* icon_spec */
5437 sipe_list_icon, /* list_icon */
5438 NULL, /* list_emblems */
5439 sipe_status_text, /* status_text */
5440 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5441 sipe_status_types, /* away_states */
5442 sipe_blist_node_menu, /* blist_node_menu */
5443 NULL, /* chat_info */
5444 NULL, /* chat_info_defaults */
5445 sipe_login, /* login */
5446 sipe_close, /* close */
5447 sipe_im_send, /* send_im */
5448 NULL, /* set_info */ // TODO maybe
5449 sipe_send_typing, /* send_typing */
5450 sipe_get_info, /* get_info */
5451 sipe_set_status, /* set_status */
5452 NULL, /* set_idle */
5453 NULL, /* change_passwd */
5454 sipe_add_buddy, /* add_buddy */
5455 NULL, /* add_buddies */
5456 sipe_remove_buddy, /* remove_buddy */
5457 NULL, /* remove_buddies */
5458 sipe_add_permit, /* add_permit */
5459 sipe_add_deny, /* add_deny */
5460 sipe_add_deny, /* rem_permit */
5461 sipe_add_permit, /* rem_deny */
5462 dummy_permit_deny, /* set_permit_deny */
5463 NULL, /* join_chat */
5464 NULL, /* reject_chat */
5465 NULL, /* get_chat_name */
5466 NULL, /* chat_invite */
5467 NULL, /* chat_leave */
5468 NULL, /* chat_whisper */
5469 NULL, /* chat_send */
5470 sipe_keep_alive, /* keepalive */
5471 NULL, /* register_user */
5472 NULL, /* get_cb_info */ // deprecated
5473 NULL, /* get_cb_away */ // deprecated
5474 sipe_alias_buddy, /* alias_buddy */
5475 sipe_group_buddy, /* group_buddy */
5476 sipe_rename_group, /* rename_group */
5477 NULL, /* buddy_free */
5478 sipe_convo_closed, /* convo_closed */
5479 purple_normalize_nocase, /* normalize */
5480 NULL, /* set_buddy_icon */
5481 sipe_remove_group, /* remove_group */
5482 NULL, /* get_cb_real_name */ // TODO?
5483 NULL, /* set_chat_topic */
5484 NULL, /* find_blist_chat */
5485 NULL, /* roomlist_get_list */
5486 NULL, /* roomlist_cancel */
5487 NULL, /* roomlist_expand_category */
5488 NULL, /* can_receive_file */
5489 NULL, /* send_file */
5490 NULL, /* new_xfer */
5491 NULL, /* offline_message */
5492 NULL, /* whiteboard_prpl_ops */
5493 sipe_send_raw, /* send_raw */
5494 NULL, /* roomlist_room_serialize */
5495 NULL, /* unregister_user */
5496 NULL, /* send_attention */
5497 NULL, /* get_attention_types */
5499 sizeof(PurplePluginProtocolInfo), /* struct_size */
5500 sipe_get_account_text_table, /* get_account_text_table */
5504 static PurplePluginInfo info = {
5505 PURPLE_PLUGIN_MAGIC,
5506 PURPLE_MAJOR_VERSION,
5507 PURPLE_MINOR_VERSION,
5508 PURPLE_PLUGIN_PROTOCOL, /**< type */
5509 NULL, /**< ui_requirement */
5510 0, /**< flags */
5511 NULL, /**< dependencies */
5512 PURPLE_PRIORITY_DEFAULT, /**< priority */
5513 "prpl-sipe", /**< id */
5514 "Microsoft LCS/OCS", /**< name */
5515 VERSION, /**< version */
5516 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5517 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5518 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5519 "Gabriel Burt <gburt@novell.com>", /**< author */
5520 PURPLE_WEBSITE, /**< homepage */
5521 sipe_plugin_load, /**< load */
5522 sipe_plugin_unload, /**< unload */
5523 sipe_plugin_destroy, /**< destroy */
5524 NULL, /**< ui_info */
5525 &prpl_info, /**< extra_info */
5526 NULL,
5527 sipe_actions,
5528 NULL,
5529 NULL,
5530 NULL,
5531 NULL
5534 static void sipe_plugin_destroy(PurplePlugin *plugin)
5536 GList *entry;
5538 entry = prpl_info.protocol_options;
5539 while (entry) {
5540 purple_account_option_destroy(entry->data);
5541 entry = g_list_delete_link(entry, entry);
5543 prpl_info.protocol_options = NULL;
5545 entry = prpl_info.user_splits;
5546 while (entry) {
5547 purple_account_user_split_destroy(entry->data);
5548 entry = g_list_delete_link(entry, entry);
5550 prpl_info.user_splits = NULL;
5553 static void init_plugin(PurplePlugin *plugin)
5555 PurpleAccountUserSplit *split;
5556 PurpleAccountOption *option;
5558 #ifdef ENABLE_NLS
5559 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5560 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5561 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5562 #endif
5564 purple_plugin_register(plugin);
5566 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5567 purple_account_user_split_set_reverse(split, FALSE);
5568 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5570 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5571 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5572 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5573 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5575 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5576 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5577 // Translators: noun (networking port)
5578 option = purple_account_option_int_new(_("Port"), "port", 5061);
5579 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5581 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5582 purple_account_option_add_list_item(option, _("Auto"), "auto");
5583 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5584 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5585 purple_account_option_add_list_item(option, _("UDP"), "udp");
5586 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5588 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5589 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5591 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5592 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5594 // TODO commented out so won't show in the preferences until we fix krb message signing
5595 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5596 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5598 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5599 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5600 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5603 option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5604 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5605 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5606 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5607 my_protocol = plugin;
5610 /* I had to redefined the function for it load, but works */
5611 gboolean purple_init_plugin(PurplePlugin *plugin){
5612 plugin->info = &(info);
5613 init_plugin((plugin));
5614 sipe_plugin_load((plugin));
5615 return purple_plugin_register(plugin);
5619 Local Variables:
5620 mode: c
5621 c-file-style: "bsd"
5622 indent-tabs-mode: t
5623 tab-width: 8
5624 End: