Fixed the support for batched subscription.
[siplcs.git] / src / sipe.c
blob55a07a907e46ea2a6997870de1964aab459604de
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, const char * buddy_name);
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(PurpleConnection *gc)
171 struct sipe_account_data *sip = gc->proto_data;
172 if (sip->transport == SIPE_TRANSPORT_UDP) {
173 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
174 gchar buf[2] = {0, 0};
175 purple_debug_info("sipe", "sending keep alive\n");
176 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
177 } else {
178 time_t now = time(NULL);
179 if ((sip->keepalive_timeout > 0) &&
180 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
181 #if PURPLE_VERSION_CHECK(2,4,0)
182 && ((now - gc->last_received) >= sip->keepalive_timeout)
183 #endif
185 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
186 sendout_pkt(gc, "\r\n\r\n");
187 sip->last_keepalive = now;
192 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
194 struct sip_connection *ret = NULL;
195 GSList *entry = sip->openconns;
196 while (entry) {
197 ret = entry->data;
198 if (ret->fd == fd) return ret;
199 entry = entry->next;
201 return NULL;
204 static void sipe_auth_free(struct sip_auth *auth)
206 g_free(auth->nonce);
207 auth->nonce = NULL;
208 g_free(auth->opaque);
209 auth->opaque = NULL;
210 g_free(auth->realm);
211 auth->realm = NULL;
212 g_free(auth->target);
213 auth->target = NULL;
214 g_free(auth->digest_session_key);
215 auth->digest_session_key = NULL;
216 g_free(auth->ntlm_key);
217 auth->ntlm_key = NULL;
218 auth->type = AUTH_TYPE_UNSET;
219 auth->retries = 0;
220 auth->expires = 0;
223 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
225 struct sip_connection *ret = g_new0(struct sip_connection, 1);
226 ret->fd = fd;
227 sip->openconns = g_slist_append(sip->openconns, ret);
228 return ret;
231 static void connection_remove(struct sipe_account_data *sip, int fd)
233 struct sip_connection *conn = connection_find(sip, fd);
234 if (conn) {
235 sip->openconns = g_slist_remove(sip->openconns, conn);
236 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
237 g_free(conn->inbuf);
238 g_free(conn);
242 static void connection_free_all(struct sipe_account_data *sip)
244 struct sip_connection *ret = NULL;
245 GSList *entry = sip->openconns;
246 while (entry) {
247 ret = entry->data;
248 connection_remove(sip, ret->fd);
249 entry = sip->openconns;
253 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
255 const gchar *method = msg->method;
256 const gchar *target = msg->target;
257 gchar noncecount[9];
258 gchar *response;
259 gchar *ret;
260 gchar *tmp = NULL;
261 const char *authdomain = sip->authdomain;
262 const char *authuser = sip->authuser;
263 //const char *krb5_realm;
264 const char *host;
265 //gchar *krb5_token = NULL;
267 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
268 // and do error checking
270 // KRB realm should always be uppercase
271 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
273 if (sip->realhostname) {
274 host = sip->realhostname;
275 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
276 host = purple_account_get_string(sip->account, "proxy", "");
277 } else {
278 host = sip->sipdomain;
281 /*gboolean new_auth = krb5_auth.gss_context == NULL;
282 if (new_auth) {
283 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
286 if (new_auth || force_reauth) {
287 krb5_token = krb5_auth.base64_token;
290 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
291 krb5_token = krb5_auth.base64_token;*/
293 if (!authdomain) {
294 authdomain = "";
297 if (!authuser || strlen(authuser) < 1) {
298 authuser = sip->username;
301 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
302 sprintf(noncecount, "%08d", auth->nc++);
303 response = purple_cipher_http_digest_calculate_response(
304 "md5", method, target, NULL, NULL,
305 auth->nonce, noncecount, NULL, auth->digest_session_key);
306 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
308 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);
309 g_free(response);
310 return ret;
311 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
312 // If we have a signature for the message, include that
313 if (msg->signature) {
314 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);
315 return tmp;
318 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
319 const gchar *ntlm_key;
320 gchar *gssapi_data;
321 #if GLIB_CHECK_VERSION(2,8,0)
322 const gchar * hostname = g_get_host_name();
323 #else
324 static char hostname[256];
325 int ret = gethostname(hostname, sizeof(hostname));
326 hostname[sizeof(hostname) - 1] = '\0';
327 if (ret == -1 || hostname[0] == '\0') {
328 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
329 g_strerror(errno);
330 strcpy(hostname, "localhost");
332 #endif
333 /*const gchar * hostname = purple_get_host_name();*/
335 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
336 auth->ntlm_key = (gchar *)ntlm_key;
337 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
338 g_free(gssapi_data);
339 return tmp;
342 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
343 return tmp;
344 } else if (auth->type == AUTH_TYPE_KERBEROS) {
345 /* Kerberos */
346 if (auth->nc == 3) {
347 /*if (new_auth || force_reauth) {
348 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
349 if (auth->opaque) {
350 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);
351 } else {
352 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
354 } else {
355 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
356 gchar * mic = "MICTODO";
357 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
358 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
359 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
360 //auth->opaque ? auth->opaque : "", auth->target);
361 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
362 //g_free(mic);
364 return tmp;
366 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
369 sprintf(noncecount, "%08d", auth->nc++);
370 response = purple_cipher_http_digest_calculate_response(
371 "md5", method, target, NULL, NULL,
372 auth->nonce, noncecount, NULL, auth->digest_session_key);
373 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
375 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);
376 g_free(response);
377 return ret;
380 static char *parse_attribute(const char *attrname, const char *source)
382 const char *tmp, *tmp2;
383 char *retval = NULL;
384 int len = strlen(attrname);
386 if (!strncmp(source, attrname, len)) {
387 tmp = source + len;
388 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
389 if (tmp2)
390 retval = g_strndup(tmp, tmp2 - tmp);
391 else
392 retval = g_strdup(tmp);
395 return retval;
398 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
400 int i = 0;
401 const char *authuser;
402 char *tmp;
403 gchar **parts;
404 //const char *krb5_realm;
405 //const char *host;
407 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
408 // and do error checking
410 // KRB realm should always be uppercase
411 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
413 if (sip->realhostname) {
414 host = sip->realhostname;
415 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
416 host = purple_account_get_string(sip->account, "proxy", "");
417 } else {
418 host = sip->sipdomain;
421 authuser = sip->authuser;
423 if (!authuser || strlen(authuser) < 1) {
424 authuser = sip->username;
427 if (!hdr) {
428 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
429 return;
432 if (!g_strncasecmp(hdr, "NTLM", 4)) {
433 auth->type = AUTH_TYPE_NTLM;
434 parts = g_strsplit(hdr+5, "\", ", 0);
435 i = 0;
436 while (parts[i]) {
437 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
438 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
439 g_free(auth->nonce);
440 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
441 g_free(tmp);
443 if ((tmp = parse_attribute("targetname=\"",
444 parts[i]))) {
445 g_free(auth->target);
446 auth->target = tmp;
448 else if ((tmp = parse_attribute("realm=\"",
449 parts[i]))) {
450 g_free(auth->realm);
451 auth->realm = tmp;
453 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
454 g_free(auth->opaque);
455 auth->opaque = tmp;
457 i++;
459 g_strfreev(parts);
460 auth->nc = 1;
461 if (!strstr(hdr, "gssapi-data")) {
462 auth->nc = 1;
463 } else {
464 auth->nc = 3;
466 return;
469 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
470 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
471 auth->type = AUTH_TYPE_KERBEROS;
472 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
473 parts = g_strsplit(hdr+9, "\", ", 0);
474 i = 0;
475 while (parts[i]) {
476 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
477 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
478 /*if (krb5_auth.gss_context == NULL) {
479 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
481 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
482 g_free(tmp);
484 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
485 g_free(auth->target);
486 auth->target = tmp;
487 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
488 g_free(auth->realm);
489 auth->realm = tmp;
490 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
491 g_free(auth->opaque);
492 auth->opaque = tmp;
494 i++;
496 g_strfreev(parts);
497 auth->nc = 3;
498 return;
501 auth->type = AUTH_TYPE_DIGEST;
502 parts = g_strsplit(hdr, " ", 0);
503 while (parts[i]) {
504 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
505 g_free(auth->nonce);
506 auth->nonce = tmp;
508 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
509 g_free(auth->realm);
510 auth->realm = tmp;
512 i++;
514 g_strfreev(parts);
516 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
517 if (auth->realm) {
518 g_free(auth->digest_session_key);
519 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
520 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
522 auth->nc = 1;
526 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
528 PurpleConnection *gc = data;
529 struct sipe_account_data *sip = gc->proto_data;
530 gsize max_write;
531 gssize written;
533 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
535 if (max_write == 0) {
536 if (sip->tx_handler != 0){
537 purple_input_remove(sip->tx_handler);
538 sip->tx_handler = 0;
540 return;
543 written = write(sip->fd, sip->txbuf->outptr, max_write);
545 if (written < 0 && errno == EAGAIN)
546 written = 0;
547 else if (written <= 0) {
548 /*TODO: do we really want to disconnect on a failure to write?*/
549 purple_connection_error(gc, _("Could not write"));
550 return;
553 purple_circ_buffer_mark_read(sip->txbuf, written);
556 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
558 PurpleConnection *gc = data;
559 struct sipe_account_data *sip = gc->proto_data;
560 gsize max_write;
561 gssize written;
563 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
565 if (max_write == 0) {
566 if (sip->tx_handler != 0) {
567 purple_input_remove(sip->tx_handler);
568 sip->tx_handler = 0;
569 return;
573 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
575 if (written < 0 && errno == EAGAIN)
576 written = 0;
577 else if (written <= 0) {
578 /*TODO: do we really want to disconnect on a failure to write?*/
579 purple_connection_error(gc, _("Could not write"));
580 return;
583 purple_circ_buffer_mark_read(sip->txbuf, written);
586 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
588 static void send_later_cb(gpointer data, gint source, const gchar *error)
590 PurpleConnection *gc = data;
591 struct sipe_account_data *sip;
592 struct sip_connection *conn;
594 if (!PURPLE_CONNECTION_IS_VALID(gc))
596 if (source >= 0)
597 close(source);
598 return;
601 if (source < 0) {
602 purple_connection_error(gc, _("Could not connect"));
603 return;
606 sip = gc->proto_data;
607 sip->fd = source;
608 sip->connecting = FALSE;
609 sip->last_keepalive = time(NULL);
611 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
613 /* If there is more to write now, we need to register a handler */
614 if (sip->txbuf->bufused > 0)
615 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
617 conn = connection_create(sip, source);
618 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
621 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
623 struct sipe_account_data *sip;
624 struct sip_connection *conn;
626 if (!PURPLE_CONNECTION_IS_VALID(gc))
628 if (gsc) purple_ssl_close(gsc);
629 return NULL;
632 sip = gc->proto_data;
633 sip->fd = gsc->fd;
634 sip->gsc = gsc;
635 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
636 sip->connecting = FALSE;
637 sip->last_keepalive = time(NULL);
639 conn = connection_create(sip, gsc->fd);
641 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
643 return sip;
646 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
648 PurpleConnection *gc = data;
649 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
650 if (sip == NULL) return;
652 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
654 /* If there is more to write now */
655 if (sip->txbuf->bufused > 0) {
656 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
661 static void sendlater(PurpleConnection *gc, const char *buf)
663 struct sipe_account_data *sip = gc->proto_data;
665 if (!sip->connecting) {
666 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
667 if (sip->transport == SIPE_TRANSPORT_TLS){
668 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
669 } else {
670 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
671 purple_connection_error(gc, _("Couldn't create socket"));
674 sip->connecting = TRUE;
677 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
678 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
680 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
683 static void sendout_pkt(PurpleConnection *gc, const char *buf)
685 struct sipe_account_data *sip = gc->proto_data;
686 time_t currtime = time(NULL);
687 int writelen = strlen(buf);
689 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
690 if (sip->transport == SIPE_TRANSPORT_UDP) {
691 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
692 purple_debug_info("sipe", "could not send packet\n");
694 } else {
695 int ret;
696 if (sip->fd < 0) {
697 sendlater(gc, buf);
698 return;
701 if (sip->tx_handler) {
702 ret = -1;
703 errno = EAGAIN;
704 } else{
705 if (sip->gsc){
706 ret = purple_ssl_write(sip->gsc, buf, writelen);
707 }else{
708 ret = write(sip->fd, buf, writelen);
712 if (ret < 0 && errno == EAGAIN)
713 ret = 0;
714 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
715 sendlater(gc, buf);
716 return;
719 if (ret < writelen) {
720 if (!sip->tx_handler){
721 if (sip->gsc){
722 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
724 else{
725 sip->tx_handler = purple_input_add(sip->fd,
726 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
727 gc);
731 /* XXX: is it OK to do this? You might get part of a request sent
732 with part of another. */
733 if (sip->txbuf->bufused > 0)
734 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
736 purple_circ_buffer_append(sip->txbuf, buf + ret,
737 writelen - ret);
742 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
744 sendout_pkt(gc, buf);
745 return len;
748 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
750 GSList *tmp = msg->headers;
751 gchar *name;
752 gchar *value;
753 GString *outstr = g_string_new("");
754 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
755 while (tmp) {
756 name = ((struct siphdrelement*) (tmp->data))->name;
757 value = ((struct siphdrelement*) (tmp->data))->value;
758 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
759 tmp = g_slist_next(tmp);
761 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
762 sendout_pkt(sip->gc, outstr->str);
763 g_string_free(outstr, TRUE);
766 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
768 gchar * buf;
769 if (sip->registrar.ntlm_key) {
770 struct sipmsg_breakdown msgbd;
771 gchar *signature_input_str;
772 msgbd.msg = msg;
773 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
774 msgbd.rand = g_strdup_printf("%08x", g_random_int());
775 sip->registrar.ntlm_num++;
776 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
777 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
778 if (signature_input_str != NULL) {
779 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
780 msg->rand = g_strdup(msgbd.rand);
781 msg->num = g_strdup(msgbd.num);
782 g_free(signature_input_str);
784 sipmsg_breakdown_free(&msgbd);
787 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
788 buf = auth_header(sip, &sip->registrar, msg);
789 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
790 sipmsg_add_header(msg, "Authorization", buf);
791 } else {
792 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
794 g_free(buf);
795 } 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")) {
796 sip->registrar.nc = 3;
797 sip->registrar.type = AUTH_TYPE_NTLM;
799 buf = auth_header(sip, &sip->registrar, msg);
800 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
801 g_free(buf);
802 } else {
803 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
807 static char *get_contact(struct sipe_account_data *sip)
809 return g_strdup(sip->contact);
813 * unused. Needed?
814 static char *get_contact_service(struct sipe_account_data *sip)
816 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()));
817 //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);
821 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
822 const char *text, const char *body)
824 gchar *name;
825 gchar *value;
826 GString *outstr = g_string_new("");
827 struct sipe_account_data *sip = gc->proto_data;
828 gchar *contact;
829 GSList *tmp;
831 sipmsg_remove_header(msg, "ms-user-data");
833 contact = get_contact(sip);
834 sipmsg_remove_header(msg, "Contact");
835 sipmsg_add_header(msg, "Contact", contact);
836 g_free(contact);
838 /* When sending the acknowlegements and errors, the content length from the original
839 message is still here, but there is no body; we need to make sure we're sending the
840 correct content length */
841 sipmsg_remove_header(msg, "Content-Length");
842 if (body) {
843 gchar len[12];
844 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
845 sipmsg_add_header(msg, "Content-Length", len);
846 } else {
847 sipmsg_remove_header(msg, "Content-Type");
848 sipmsg_add_header(msg, "Content-Length", "0");
851 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
852 //gchar * mic = "MICTODO";
853 msg->response = code;
855 sipmsg_remove_header(msg, "Authentication-Info");
856 sign_outgoing_message(msg, sip, msg->method);
858 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
859 tmp = msg->headers;
860 while (tmp) {
861 name = ((struct siphdrelement*) (tmp->data))->name;
862 value = ((struct siphdrelement*) (tmp->data))->value;
864 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
865 tmp = g_slist_next(tmp);
867 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
868 sendout_pkt(gc, outstr->str);
869 g_string_free(outstr, TRUE);
872 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
874 if (trans->msg) sipmsg_free(trans->msg);
875 sip->transactions = g_slist_remove(sip->transactions, trans);
876 g_free(trans);
879 static struct transaction *
880 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
882 struct transaction *trans = g_new0(struct transaction, 1);
883 trans->time = time(NULL);
884 trans->msg = (struct sipmsg *)msg;
885 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
886 trans->callback = callback;
887 sip->transactions = g_slist_append(sip->transactions, trans);
888 return trans;
891 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
893 struct transaction *trans;
894 GSList *transactions = sip->transactions;
895 gchar *cseq = sipmsg_find_header(msg, "CSeq");
897 while (transactions) {
898 trans = transactions->data;
899 if (!strcmp(trans->cseq, cseq)) {
900 return trans;
902 transactions = transactions->next;
905 return NULL;
908 static struct transaction *
909 send_sip_request(PurpleConnection *gc, const gchar *method,
910 const gchar *url, const gchar *to, const gchar *addheaders,
911 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
913 struct sipe_account_data *sip = gc->proto_data;
914 const char *addh = "";
915 char *buf;
916 struct sipmsg *msg;
917 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
918 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
919 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
920 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
921 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
922 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
923 gchar *route = strdup("");
924 gchar *epid = get_epid(sip); // TODO generate one per account/login
925 struct transaction *trans;
927 if (dialog && dialog->routes)
929 GSList *iter = dialog->routes;
931 while(iter)
933 char *tmp = route;
934 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
935 g_free(tmp);
936 iter = g_slist_next(iter);
940 if (!ourtag && !dialog) {
941 ourtag = gentag();
944 if (!strcmp(method, "REGISTER")) {
945 if (sip->regcallid) {
946 g_free(callid);
947 callid = g_strdup(sip->regcallid);
948 } else {
949 sip->regcallid = g_strdup(callid);
953 if (addheaders) addh = addheaders;
955 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
956 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
957 "From: <sip:%s>%s%s;epid=%s\r\n"
958 "To: <%s>%s%s%s%s\r\n"
959 "Max-Forwards: 70\r\n"
960 "CSeq: %d %s\r\n"
961 "User-Agent: %s\r\n"
962 "Call-ID: %s\r\n"
963 "%s%s"
964 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
965 method,
966 dialog && dialog->request ? dialog->request : url,
967 TRANSPORT_DESCRIPTOR,
968 purple_network_get_my_ip(-1),
969 sip->listenport,
970 branch ? ";branch=" : "",
971 branch ? branch : "",
972 sip->username,
973 ourtag ? ";tag=" : "",
974 ourtag ? ourtag : "",
975 epid,
977 theirtag ? ";tag=" : "",
978 theirtag ? theirtag : "",
979 theirepid ? ";epid=" : "",
980 theirepid ? theirepid : "",
981 dialog ? ++dialog->cseq : ++sip->cseq,
982 method,
983 useragent,
984 callid,
985 route,
986 addh,
987 body ? strlen(body) : 0,
988 body ? body : "");
991 //printf ("parsing msg buf:\n%s\n\n", buf);
992 msg = sipmsg_parse_msg(buf);
994 g_free(buf);
995 g_free(ourtag);
996 g_free(theirtag);
997 g_free(theirepid);
998 g_free(branch);
999 g_free(callid);
1000 g_free(route);
1001 g_free(epid);
1003 sign_outgoing_message (msg, sip, method);
1005 buf = sipmsg_to_string (msg);
1007 /* add to ongoing transactions */
1008 trans = transactions_add_buf(sip, msg, tc);
1009 sendout_pkt(gc, buf);
1010 g_free(buf);
1012 return trans;
1015 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1017 gchar *from = g_strdup_printf("sip:%s", sip->username);
1018 gchar *contact = get_contact(sip);
1019 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1020 "Content-Type: application/SOAP+xml\r\n",contact);
1022 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1023 tr->payload = payload;
1025 g_free(from);
1026 g_free(contact);
1027 g_free(hdr);
1030 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1032 send_soap_request_with_cb(sip, body, NULL, NULL);
1035 static char *get_contact_register(struct sipe_account_data *sip)
1037 char *epid = get_epid(sip);
1038 char *uuid = generateUUIDfromEPID(epid);
1039 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);
1040 g_free(uuid);
1041 g_free(epid);
1042 return(buf);
1045 static void do_register_exp(struct sipe_account_data *sip, int expire)
1047 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1048 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1049 char *to = g_strdup_printf("sip:%s", sip->username);
1050 char *contact = get_contact_register(sip);
1051 char *hdr = g_strdup_printf("Contact: %s\r\n"
1052 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1053 "Event: registration\r\n"
1054 "Allow-Events: presence\r\n"
1055 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1056 "%s", contact, expires);
1057 g_free(contact);
1058 g_free(expires);
1060 sip->registerstatus = 1;
1062 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1063 process_register_response);
1065 g_free(hdr);
1066 g_free(uri);
1067 g_free(to);
1070 static void do_register_cb(struct sipe_account_data *sip)
1072 do_register_exp(sip, -1);
1073 sip->reregister_set = FALSE;
1076 static void do_register(struct sipe_account_data *sip)
1078 do_register_exp(sip, -1);
1082 * Returns URI from provided To or From header.
1084 * Needs to g_free() after use.
1086 * @return URI with sip: prefix
1088 static gchar *parse_from(const gchar *hdr)
1090 gchar *from;
1091 const gchar *tmp, *tmp2 = hdr;
1093 if (!hdr) return NULL;
1094 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1095 tmp = strchr(hdr, '<');
1097 /* i hate the different SIP UA behaviours... */
1098 if (tmp) { /* sip address in <...> */
1099 tmp2 = tmp + 1;
1100 tmp = strchr(tmp2, '>');
1101 if (tmp) {
1102 from = g_strndup(tmp2, tmp - tmp2);
1103 } else {
1104 purple_debug_info("sipe", "found < without > in From\n");
1105 return NULL;
1107 } else {
1108 tmp = strchr(tmp2, ';');
1109 if (tmp) {
1110 from = g_strndup(tmp2, tmp - tmp2);
1111 } else {
1112 from = g_strdup(tmp2);
1115 purple_debug_info("sipe", "got %s\n", from);
1116 return from;
1119 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1121 va_list args;
1122 xmlnode * node = NULL;
1123 const gchar * name;
1125 va_start(args, parent);
1126 while ((name = va_arg(args, const char *)) != NULL) {
1127 node = xmlnode_get_child(parent, name);
1128 if (node == NULL) return NULL;
1129 parent = node;
1131 va_end(args);
1133 return node;
1137 static void
1138 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1140 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1141 send_soap_request(sip, body);
1142 g_free(body);
1145 static void
1146 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1148 if (allow) {
1149 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1150 } else {
1151 purple_debug_info("sipe", "Blocking contact %s\n", who);
1154 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1157 static
1158 void sipe_auth_user_cb(void * data)
1160 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1161 if (!job) return;
1163 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1164 g_free(job);
1167 static
1168 void sipe_deny_user_cb(void * data)
1170 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1171 if (!job) return;
1173 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1174 g_free(job);
1177 static void
1178 sipe_add_permit(PurpleConnection *gc, const char *name)
1180 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1181 sipe_contact_allow_deny(sip, name, TRUE);
1184 static void
1185 sipe_add_deny(PurpleConnection *gc, const char *name)
1187 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1188 sipe_contact_allow_deny(sip, name, FALSE);
1191 /*static void
1192 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1194 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1195 sipe_contact_set_acl(sip, name, "");
1198 static void
1199 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1201 xmlnode *watchers;
1202 xmlnode *watcher;
1203 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1204 if (msg->response != 0 && msg->response != 200) return;
1206 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1208 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1209 if (!watchers) return;
1211 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1212 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1213 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1214 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1216 // TODO pull out optional displayName to pass as alias
1217 if (remote_user) {
1218 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1219 job->who = remote_user;
1220 job->sip = sip;
1221 purple_account_request_authorization(
1222 sip->account,
1223 remote_user,
1224 NULL, // id
1225 alias,
1226 NULL, // message
1227 on_list,
1228 sipe_auth_user_cb,
1229 sipe_deny_user_cb,
1230 (void *) job);
1235 xmlnode_free(watchers);
1236 return;
1239 static void
1240 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1242 PurpleGroup * purple_group = purple_find_group(group->name);
1243 if (!purple_group) {
1244 purple_group = purple_group_new(group->name);
1245 purple_blist_add_group(purple_group, NULL);
1248 if (purple_group) {
1249 group->purple_group = purple_group;
1250 sip->groups = g_slist_append(sip->groups, group);
1251 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1252 } else {
1253 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1257 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1259 struct sipe_group *group;
1260 GSList *entry;
1261 if (sip == NULL) {
1262 return NULL;
1265 entry = sip->groups;
1266 while (entry) {
1267 group = entry->data;
1268 if (group->id == id) {
1269 return group;
1271 entry = entry->next;
1273 return NULL;
1276 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1278 struct sipe_group *group;
1279 GSList *entry;
1280 if (sip == NULL) {
1281 return NULL;
1284 entry = sip->groups;
1285 while (entry) {
1286 group = entry->data;
1287 if (!strcmp(group->name, name)) {
1288 return group;
1290 entry = entry->next;
1292 return NULL;
1295 static void
1296 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1298 gchar *body;
1299 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1300 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1301 send_soap_request(sip, body);
1302 g_free(body);
1303 g_free(group->name);
1304 group->name = g_strdup(name);
1308 * Only appends if no such value already stored.
1309 * Like Set in Java.
1311 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1312 GSList * res = list;
1313 if (!g_slist_find_custom(list, data, func)) {
1314 res = g_slist_insert_sorted(list, data, func);
1316 return res;
1319 static int
1320 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1321 return group1->id - group2->id;
1325 * Returns string like "2 4 7 8" - group ids buddy belong to.
1327 static gchar *
1328 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1329 int i = 0;
1330 gchar *res;
1331 //creating array from GList, converting int to gchar*
1332 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1333 GSList *entry = buddy->groups;
1334 while (entry) {
1335 struct sipe_group * group = entry->data;
1336 ids_arr[i] = g_strdup_printf("%d", group->id);
1337 entry = entry->next;
1338 i++;
1340 ids_arr[i] = NULL;
1341 res = g_strjoinv(" ", ids_arr);
1342 g_strfreev(ids_arr);
1343 return res;
1347 * Sends buddy update to server
1349 static void
1350 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1352 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1353 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1355 if (buddy && purple_buddy) {
1356 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1357 gchar *body;
1358 gchar *groups = sipe_get_buddy_groups_string(buddy);
1359 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1361 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1362 alias, groups, "true", buddy->name, sip->contacts_delta++
1364 send_soap_request(sip, body);
1365 g_free(groups);
1366 g_free(body);
1370 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1372 if (msg->response == 200) {
1373 struct sipe_group *group;
1374 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1375 xmlnode *xml;
1376 xmlnode *node;
1377 char *group_id;
1378 struct sipe_buddy *buddy;
1380 xml = xmlnode_from_str(msg->body, msg->bodylen);
1381 if (!xml) {
1382 g_free(ctx);
1383 return FALSE;
1386 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1387 if (!node) {
1388 g_free(ctx);
1389 xmlnode_free(xml);
1390 return FALSE;
1393 group_id = xmlnode_get_data(node);
1394 if (!group_id) {
1395 g_free(ctx);
1396 xmlnode_free(xml);
1397 return FALSE;
1400 group = g_new0(struct sipe_group, 1);
1401 group->id = (int)g_ascii_strtod(group_id, NULL);
1402 g_free(group_id);
1403 group->name = ctx->group_name;
1405 sipe_group_add(sip, group);
1407 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1408 if (buddy) {
1409 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1412 sipe_group_set_user(sip, ctx->user_name);
1414 g_free(ctx);
1415 xmlnode_free(xml);
1416 return TRUE;
1418 return FALSE;
1421 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1423 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1424 gchar *body;
1425 ctx->group_name = g_strdup(name);
1426 ctx->user_name = g_strdup(who);
1428 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1429 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1430 g_free(body);
1434 * A timer callback
1435 * Should return FALSE if repetitive action is not needed
1437 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1439 gboolean ret;
1440 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1441 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1442 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1443 (sched_action->action)(sched_action->sip, sched_action->payload);
1444 ret = sched_action->repetitive;
1445 g_free(sched_action->payload);
1446 g_free(sched_action->name);
1447 g_free(sched_action);
1448 return ret;
1452 * Do schedule action for execution in the future.
1453 * Non repetitive execution.
1455 * @param name of action (will be copied)
1456 * @param timeout in seconds
1457 * @action callback function
1458 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1460 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1462 struct scheduled_action *sched_action;
1464 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1465 sched_action = g_new0(struct scheduled_action, 1);
1466 sched_action->repetitive = FALSE;
1467 sched_action->name = g_strdup(name);
1468 sched_action->action = action;
1469 sched_action->sip = sip;
1470 sched_action->payload = payload;
1471 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1472 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1473 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1477 * Kills action timer effectively cancelling
1478 * scheduled action
1480 * @param name of action
1482 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1484 GSList *entry;
1486 if (!sip->timeouts || !name) return;
1488 entry = sip->timeouts;
1489 while (entry) {
1490 struct scheduled_action *sched_action = entry->data;
1491 if(!strcmp(sched_action->name, name)) {
1492 GSList *to_delete = entry;
1493 entry = entry->next;
1494 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1495 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1496 purple_timeout_remove(sched_action->timeout_handler);
1497 g_free(sched_action->payload);
1498 g_free(sched_action->name);
1499 g_free(sched_action);
1500 } else {
1501 entry = entry->next;
1506 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1508 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1510 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1512 process_incoming_notify(sip, msg, FALSE, FALSE);
1514 return TRUE;
1517 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1519 gchar *tmp = *resources_uri;
1520 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1521 g_free(tmp);
1524 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1526 gchar *tmp = *resources_uri;
1527 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1528 if(sbuddy){
1529 if(!sbuddy->resubscribed){ //Only not resubscribed contacts; the first time everybody are included
1530 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1531 sbuddy->resubscribed = FALSE;
1534 g_free(tmp);
1538 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1539 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1540 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1541 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1542 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1545 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip, const char * buddy_name){
1546 gchar *to = g_strdup_printf("sip:%s", sip->username);
1547 gchar *contact = get_contact(sip);
1548 gchar *request;
1549 gchar *content;
1550 gchar *resources_uri = g_strdup("");
1551 gchar *require = "";
1552 gchar *accept = "";
1553 gchar *autoextend = "";
1554 gchar *content_type;
1557 if (sip->msrtc_event_categories) {
1559 if(!buddy_name){
1560 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1562 else
1564 sipe_subscribe_resource_uri_with_context(buddy_name,NULL, &resources_uri);
1566 require = ", categoryList";
1567 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1568 content_type = "application/msrtc-adrl-categorylist+xml";
1569 content = g_strdup_printf(
1570 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1571 "<action name=\"subscribe\" id=\"63792024\">\n"
1572 "<adhocList>\n%s</adhocList>\n"
1573 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1574 "<category name=\"note\"/>\n"
1575 "<category name=\"state\"/>\n"
1576 "</categoryList>\n"
1577 "</action>\n"
1578 "</batchSub>", sip->username, resources_uri);
1579 } else {
1580 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1581 autoextend = "Supported: com.microsoft.autoextend\r\n";
1582 content_type = "application/adrl+xml";
1583 content = g_strdup_printf(
1584 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1585 "<create xmlns=\"\">\n%s</create>\n"
1586 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1588 g_free(resources_uri);
1590 request = g_strdup_printf(
1591 "Require: adhoclist%s\r\n"
1592 "Supported: eventlist\r\n"
1593 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1594 "Supported: ms-piggyback-first-notify\r\n"
1595 "%sSupported: ms-benotify\r\n"
1596 "Proxy-Require: ms-benotify\r\n"
1597 "Event: presence\r\n"
1598 "Content-Type: %s\r\n"
1599 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1600 g_free(contact);
1602 /* subscribe to buddy presence */
1603 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1604 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1606 g_free(content);
1607 g_free(to);
1608 g_free(request);
1612 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1613 * The user sends a single SUBSCRIBE request to the subscribed contact.
1614 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1618 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1620 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1621 gchar *tmp = get_contact(sip);
1622 gchar *request;
1623 gchar *content;
1624 gchar *autoextend = "";
1626 if (!sip->msrtc_event_categories)
1627 autoextend = "Supported: com.microsoft.autoextend\r\n";
1629 request = g_strdup_printf(
1630 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1631 "Supported: ms-piggyback-first-notify\r\n"
1632 "%sSupported: ms-benotify\r\n"
1633 "Proxy-Require: ms-benotify\r\n"
1634 "Event: presence\r\n"
1635 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1636 "Contact: %s\r\n", autoextend,tmp);
1638 content = g_strdup_printf(
1639 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1640 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1641 "<resource uri=\"%s\"/>\n"
1642 "</adhocList>\n"
1643 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1644 "<category name=\"note\"/>\n"
1645 "<category name=\"state\"/>\n"
1646 "</categoryList>\n"
1647 "</action>\n"
1648 "</batchSub>", sip->username, to
1651 g_free(tmp);
1653 /* subscribe to buddy presence */
1654 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1656 g_free(content);
1657 g_free(to);
1658 g_free(request);
1661 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1663 if (!purple_status_is_active(status))
1664 return;
1666 if (account->gc) {
1667 struct sipe_account_data *sip = account->gc->proto_data;
1669 if (sip) {
1670 g_free(sip->status);
1671 sip->status = g_strdup(purple_status_get_id(status));
1672 send_presence_status(sip);
1677 static void
1678 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1680 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1681 sipe_group_set_user(sip, name);
1684 static void
1685 sipe_group_buddy(PurpleConnection *gc,
1686 const char *who,
1687 const char *old_group_name,
1688 const char *new_group_name)
1690 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1691 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1692 struct sipe_group * old_group = NULL;
1693 struct sipe_group * new_group;
1695 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1696 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1698 if(!buddy) { // buddy not in roaming list
1699 return;
1702 if (old_group_name) {
1703 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1705 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1707 if (old_group) {
1708 buddy->groups = g_slist_remove(buddy->groups, old_group);
1709 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1712 if (!new_group) {
1713 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1714 } else {
1715 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1716 sipe_group_set_user(sip, who);
1720 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1722 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1723 struct sipe_buddy *b;
1725 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1727 // Prepend sip: if needed
1728 if (strncmp("sip:", buddy->name, 4)) {
1729 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1730 purple_blist_rename_buddy(buddy, buf);
1731 g_free(buf);
1734 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1735 b = g_new0(struct sipe_buddy, 1);
1736 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1737 b->name = g_strdup(buddy->name);
1738 g_hash_table_insert(sip->buddies, b->name, b);
1739 sipe_group_buddy(gc, b->name, NULL, group->name);
1740 if(sip->batched_support){
1741 sipe_subscribe_presence_batched(sip, b->name);
1742 }else{
1743 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1745 } else {
1746 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1750 static void sipe_free_buddy(struct sipe_buddy *buddy)
1752 g_free(buddy->name);
1753 g_free(buddy->annotation);
1754 g_free(buddy->device_name);
1755 g_slist_free(buddy->groups);
1756 g_free(buddy);
1760 * Unassociates buddy from group first.
1761 * Then see if no groups left, removes buddy completely.
1762 * Otherwise updates buddy groups on server.
1764 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1766 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1767 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1768 struct sipe_group *g = NULL;
1770 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1772 if (!b) return;
1774 if (group) {
1775 g = sipe_group_find_by_name(sip, group->name);
1778 if (g) {
1779 b->groups = g_slist_remove(b->groups, g);
1780 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1783 if (g_slist_length(b->groups) < 1) {
1784 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1785 sipe_cancel_scheduled_action(sip, action_name);
1786 g_free(action_name);
1788 g_hash_table_remove(sip->buddies, buddy->name);
1790 if (b->name) {
1791 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1792 send_soap_request(sip, body);
1793 g_free(body);
1796 sipe_free_buddy(b);
1797 } else {
1798 //updates groups on server
1799 sipe_group_set_user(sip, b->name);
1804 static void
1805 sipe_rename_group(PurpleConnection *gc,
1806 const char *old_name,
1807 PurpleGroup *group,
1808 GList *moved_buddies)
1810 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1811 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1812 if (group) {
1813 sipe_group_rename(sip, s_group, group->name);
1814 } else {
1815 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1819 static void
1820 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1822 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1823 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1824 if (s_group) {
1825 gchar *body;
1826 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1827 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1828 send_soap_request(sip, body);
1829 g_free(body);
1831 sip->groups = g_slist_remove(sip->groups, s_group);
1832 g_free(s_group->name);
1833 g_free(s_group);
1834 } else {
1835 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1839 static GList *sipe_status_types(PurpleAccount *acc)
1841 PurpleStatusType *type;
1842 GList *types = NULL;
1844 // Online
1845 type = purple_status_type_new_with_attrs(
1846 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1847 // Translators: noun
1848 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1849 NULL);
1850 types = g_list_append(types, type);
1852 // Busy
1853 type = purple_status_type_new_with_attrs(
1854 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1855 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1856 NULL);
1857 types = g_list_append(types, type);
1859 // Do Not Disturb (not user settable)
1860 type = purple_status_type_new_with_attrs(
1861 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1862 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1863 NULL);
1864 types = g_list_append(types, type);
1866 // Be Right Back
1867 type = purple_status_type_new_with_attrs(
1868 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1869 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1870 NULL);
1871 types = g_list_append(types, type);
1873 // Away
1874 type = purple_status_type_new_with_attrs(
1875 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1876 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1877 NULL);
1878 types = g_list_append(types, type);
1880 //On The Phone
1881 type = purple_status_type_new_with_attrs(
1882 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1883 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1884 NULL);
1885 types = g_list_append(types, type);
1887 //Out To Lunch
1888 type = purple_status_type_new_with_attrs(
1889 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1890 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1891 NULL);
1892 types = g_list_append(types, type);
1894 //Appear Offline
1895 type = purple_status_type_new_full(
1896 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1897 types = g_list_append(types, type);
1899 // Offline
1900 type = purple_status_type_new_full(
1901 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1902 types = g_list_append(types, type);
1904 return types;
1908 * A callback for g_hash_table_foreach
1910 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1912 sipe_subscribe_presence_single(sip, buddy->name);
1916 * Removes entries from purple buddy list
1917 * that does not correspond ones in the roaming contact list.
1919 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1920 GSList *buddies = purple_find_buddies(sip->account, NULL);
1921 GSList *entry = buddies;
1922 struct sipe_buddy *buddy;
1923 PurpleBuddy *b;
1924 PurpleGroup *g;
1926 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1927 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1928 while (entry) {
1929 b = entry->data;
1930 g = purple_buddy_get_group(b);
1931 buddy = g_hash_table_lookup(sip->buddies, b->name);
1932 if(buddy) {
1933 gboolean in_sipe_groups = FALSE;
1934 GSList *entry2 = buddy->groups;
1935 while (entry2) {
1936 struct sipe_group *group = entry2->data;
1937 if (!strcmp(group->name, g->name)) {
1938 in_sipe_groups = TRUE;
1939 break;
1941 entry2 = entry2->next;
1943 if(!in_sipe_groups) {
1944 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1945 purple_blist_remove_buddy(b);
1947 } else {
1948 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1949 purple_blist_remove_buddy(b);
1951 entry = entry->next;
1955 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1957 int len = msg->bodylen;
1959 gchar *tmp = sipmsg_find_header(msg, "Event");
1960 xmlnode *item;
1961 xmlnode *isc;
1962 const gchar *contacts_delta;
1963 xmlnode *group_node;
1964 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1965 return FALSE;
1968 /* Convert the contact from XML to Purple Buddies */
1969 isc = xmlnode_from_str(msg->body, len);
1970 if (!isc) {
1971 return FALSE;
1974 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1975 if (contacts_delta) {
1976 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1979 /* Parse groups */
1980 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1981 struct sipe_group * group = g_new0(struct sipe_group, 1);
1982 const char *name = xmlnode_get_attrib(group_node, "name");
1984 if (!strncmp(name, "~", 1)) {
1985 // TODO translate
1986 name = "Other Contacts";
1988 group->name = g_strdup(name);
1989 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1991 sipe_group_add(sip, group);
1994 // Make sure we have at least one group
1995 if (g_slist_length(sip->groups) == 0) {
1996 struct sipe_group * group = g_new0(struct sipe_group, 1);
1997 PurpleGroup *purple_group;
1998 // TODO translate
1999 group->name = g_strdup("Other Contacts");
2000 group->id = 1;
2001 purple_group = purple_group_new(group->name);
2002 purple_blist_add_group(purple_group, NULL);
2003 sip->groups = g_slist_append(sip->groups, group);
2006 /* Parse contacts */
2007 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2008 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
2009 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
2010 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
2011 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
2012 gchar **item_groups;
2013 struct sipe_group *group = NULL;
2014 struct sipe_buddy *buddy = NULL;
2015 int i = 0;
2017 // assign to group Other Contacts if nothing else received
2018 if(!groups || !strcmp("", groups) ) {
2019 group = sipe_group_find_by_name(sip, "Other Contacts");
2020 groups = group ? g_strdup_printf("%d", group->id) : "1";
2023 item_groups = g_strsplit(groups, " ", 0);
2025 while (item_groups[i]) {
2026 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2028 // If couldn't find the right group for this contact, just put them in the first group we have
2029 if (group == NULL && g_slist_length(sip->groups) > 0) {
2030 group = sip->groups->data;
2033 if (group != NULL) {
2034 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2035 if (!b){
2036 b = purple_buddy_new(sip->account, buddy_name, uri);
2037 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2040 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2041 if (name != NULL && strlen(name) != 0) {
2042 purple_blist_alias_buddy(b, name);
2046 if (!buddy) {
2047 buddy = g_new0(struct sipe_buddy, 1);
2048 buddy->name = g_strdup(b->name);
2049 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2052 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2054 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2055 } else {
2056 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2057 name);
2060 i++;
2061 } // while, contact groups
2062 g_strfreev(item_groups);
2063 g_free(groups);
2064 g_free(name);
2065 g_free(buddy_name);
2066 g_free(uri);
2068 } // for, contacts
2070 xmlnode_free(isc);
2072 sipe_cleanup_local_blist(sip);
2074 //subscribe to buddies
2075 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2076 if(sip->batched_support){
2077 sipe_subscribe_presence_batched(sip, NULL);
2079 else{
2080 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2082 sip->subscribed_buddies = TRUE;
2085 return 0;
2089 * Subscribe roaming contacts
2091 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2093 gchar *to = g_strdup_printf("sip:%s", sip->username);
2094 gchar *tmp = get_contact(sip);
2095 gchar *hdr = g_strdup_printf(
2096 "Event: vnd-microsoft-roaming-contacts\r\n"
2097 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2098 "Supported: com.microsoft.autoextend\r\n"
2099 "Supported: ms-benotify\r\n"
2100 "Proxy-Require: ms-benotify\r\n"
2101 "Supported: ms-piggyback-first-notify\r\n"
2102 "Contact: %s\r\n", tmp);
2103 g_free(tmp);
2105 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2106 g_free(to);
2107 g_free(hdr);
2110 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2112 gchar *to = g_strdup_printf("sip:%s", sip->username);
2113 gchar *tmp = get_contact(sip);
2114 gchar *hdr = g_strdup_printf(
2115 "Event: presence.wpending\r\n"
2116 "Accept: text/xml+msrtc.wpending\r\n"
2117 "Supported: com.microsoft.autoextend\r\n"
2118 "Supported: ms-benotify\r\n"
2119 "Proxy-Require: ms-benotify\r\n"
2120 "Supported: ms-piggyback-first-notify\r\n"
2121 "Contact: %s\r\n", tmp);
2122 g_free(tmp);
2124 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2125 g_free(to);
2126 g_free(hdr);
2130 * Fires on deregistration event initiated by server.
2131 * [MS-SIPREGE] SIP extension.
2134 // 2007 Example
2136 // Content-Type: text/registration-event
2137 // subscription-state: terminated;expires=0
2138 // ms-diagnostics-public: 4141;reason="User disabled"
2140 // deregistered;event=rejected
2142 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2144 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2145 gchar *event = NULL;
2146 gchar *reason = NULL;
2147 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2149 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2150 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2152 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2153 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2154 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2155 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2156 } else {
2157 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2158 return;
2161 if (warning != NULL) {
2162 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2163 } else { // for LCS2005
2164 int error_id = 0;
2165 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2166 error_id = 4140; // [MS-SIPREGE]
2167 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2168 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2169 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2170 error_id = 4141;
2171 reason = g_strdup(_("User disabled")); // [MS-OCER]
2172 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2173 error_id = 4142;
2174 reason = g_strdup(_("User moved")); // [MS-OCER]
2177 g_free(event);
2178 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2179 g_free(reason);
2181 sip->gc->wants_to_die = TRUE;
2182 purple_connection_error(sip->gc, warning);
2183 g_free(warning);
2187 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2189 const gchar *contacts_delta;
2190 xmlnode *xml;
2192 xml = xmlnode_from_str(msg->body, msg->bodylen);
2193 if (!xml)
2195 return;
2198 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2199 if (contacts_delta)
2201 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2204 xmlnode_free(xml);
2210 * When we receive some self (BE) NOTIFY with a new subscriber
2211 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2215 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2217 gchar *contact;
2218 gchar *to;
2219 xmlnode *xml;
2220 xmlnode *node;
2221 char *display_name = NULL;
2222 PurpleBuddy *pbuddy;
2223 const char *alias;
2224 char *uri_alias;
2225 char *uri_user;
2227 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2229 xml = xmlnode_from_str(msg->body, msg->bodylen);
2230 if (!xml) return;
2232 contact = get_contact(sip);
2233 to = g_strdup_printf("sip:%s", sip->username);
2235 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2236 const char *user;
2237 const char *acknowledged;
2238 gchar *hdr;
2239 gchar *body;
2241 user = xmlnode_get_attrib(node, "user");
2242 if (!user) continue;
2243 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2244 uri_user = g_strdup_printf("sip:%s", user);
2245 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2246 if(pbuddy){
2247 alias = purple_buddy_get_local_alias(pbuddy);
2248 uri_alias = g_strdup_printf("sip:%s", alias);
2249 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2250 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2251 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2252 purple_blist_alias_buddy(pbuddy, display_name);
2254 g_free(uri_alias);
2256 g_free(uri_user);
2258 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2259 if(!g_ascii_strcasecmp(acknowledged,"false")){
2260 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2261 hdr = g_strdup_printf(
2262 "Contact: %s\r\n"
2263 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2265 body = g_strdup_printf(
2266 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2267 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2268 "</setSubscribers>", user);
2270 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2271 g_free(body);
2272 g_free(hdr);
2276 g_free(to);
2277 g_free(contact);
2278 xmlnode_free(xml);
2281 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2283 gchar *to = g_strdup_printf("sip:%s", sip->username);
2284 gchar *tmp = get_contact(sip);
2285 gchar *hdr = g_strdup_printf(
2286 "Event: vnd-microsoft-roaming-ACL\r\n"
2287 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2288 "Supported: com.microsoft.autoextend\r\n"
2289 "Supported: ms-benotify\r\n"
2290 "Proxy-Require: ms-benotify\r\n"
2291 "Supported: ms-piggyback-first-notify\r\n"
2292 "Contact: %s\r\n", tmp);
2293 g_free(tmp);
2295 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2296 g_free(to);
2297 g_free(hdr);
2301 * To request for presence information about the user, access level settings that have already been configured by the user
2302 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2303 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2306 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2308 gchar *to = g_strdup_printf("sip:%s", sip->username);
2309 gchar *tmp = get_contact(sip);
2310 gchar *hdr = g_strdup_printf(
2311 "Event: vnd-microsoft-roaming-self\r\n"
2312 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2313 "Supported: ms-benotify\r\n"
2314 "Proxy-Require: ms-benotify\r\n"
2315 "Supported: ms-piggyback-first-notify\r\n"
2316 "Contact: %s\r\n"
2317 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2319 gchar *body=g_strdup(
2320 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2321 "<roaming type=\"categories\"/>"
2322 "<roaming type=\"containers\"/>"
2323 "<roaming type=\"subscribers\"/></roamingList>");
2325 g_free(tmp);
2326 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2327 g_free(body);
2328 g_free(to);
2329 g_free(hdr);
2333 * For 2005 version
2335 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2337 gchar *to = g_strdup_printf("sip:%s", sip->username);
2338 gchar *tmp = get_contact(sip);
2339 gchar *hdr = g_strdup_printf(
2340 "Event: vnd-microsoft-provisioning\r\n"
2341 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2342 "Supported: com.microsoft.autoextend\r\n"
2343 "Supported: ms-benotify\r\n"
2344 "Proxy-Require: ms-benotify\r\n"
2345 "Supported: ms-piggyback-first-notify\r\n"
2346 "Expires: 0\r\n"
2347 "Contact: %s\r\n", tmp);
2349 g_free(tmp);
2350 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2351 g_free(to);
2352 g_free(hdr);
2355 /** Subscription for provisioning information to help with initial
2356 * configuration. This subscription is a one-time query (denoted by the Expires header,
2357 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2358 * configuration, meeting policies, and policy settings that Communicator must enforce.
2359 * TODO: for what we need this information.
2362 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2364 gchar *to = g_strdup_printf("sip:%s", sip->username);
2365 gchar *tmp = get_contact(sip);
2366 gchar *hdr = g_strdup_printf(
2367 "Event: vnd-microsoft-provisioning-v2\r\n"
2368 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2369 "Supported: com.microsoft.autoextend\r\n"
2370 "Supported: ms-benotify\r\n"
2371 "Proxy-Require: ms-benotify\r\n"
2372 "Supported: ms-piggyback-first-notify\r\n"
2373 "Expires: 0\r\n"
2374 "Contact: %s\r\n"
2375 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2376 gchar *body = g_strdup(
2377 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2378 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2379 "<provisioningGroup name=\"ucPolicy\"/>"
2380 "</provisioningGroupList>");
2382 g_free(tmp);
2383 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2384 g_free(body);
2385 g_free(to);
2386 g_free(hdr);
2389 /* IM Session (INVITE and MESSAGE methods) */
2391 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2393 struct sip_im_session *session;
2394 GSList *entry;
2395 if (sip == NULL || who == NULL) {
2396 return NULL;
2399 entry = sip->im_sessions;
2400 while (entry) {
2401 session = entry->data;
2402 if ((who != NULL && !strcmp(who, session->with))) {
2403 return session;
2405 entry = entry->next;
2407 return NULL;
2410 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2412 struct sip_im_session *session = find_im_session(sip, who);
2413 if (!session) {
2414 session = g_new0(struct sip_im_session, 1);
2415 session->with = g_strdup(who);
2416 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2417 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2419 return session;
2422 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2424 struct sip_dialog *dialog = session->dialog;
2425 GSList *entry;
2427 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2429 if (dialog) {
2430 entry = dialog->routes;
2431 while (entry) {
2432 g_free(entry->data);
2433 entry = g_slist_remove(entry, entry->data);
2435 entry = dialog->supported;
2436 while (entry) {
2437 g_free(entry->data);
2438 entry = g_slist_remove(entry, entry->data);
2440 g_free(dialog->callid);
2441 g_free(dialog->ourtag);
2442 g_free(dialog->theirtag);
2443 g_free(dialog->theirepid);
2444 g_free(dialog->request);
2446 g_free(session->dialog);
2448 entry = session->outgoing_message_queue;
2449 while (entry) {
2450 g_free(entry->data);
2451 entry = g_slist_remove(entry, entry->data);
2454 g_hash_table_destroy(session->unconfirmed_messages);
2456 g_free(session->with);
2457 g_free(session);
2460 static gboolean
2461 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2463 gboolean ret = TRUE;
2465 if (msg->response != 200) {
2466 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2467 return FALSE;
2470 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2472 return ret;
2476 * Asks UA/proxy about its capabilities.
2478 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2480 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2481 gchar *contact = get_contact(sip);
2482 gchar *request;
2483 request = g_strdup_printf(
2484 "Accept: application/sdp\r\n"
2485 "Contact: %s\r\n", contact);
2487 g_free(contact);
2489 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2491 g_free(to);
2492 g_free(request);
2495 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2497 char *msg, *msg_tmp;
2498 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2499 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2500 g_free(msg_tmp);
2501 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2502 "possibly because one or more persons are offline:\n%s") ,
2503 msg ? msg : "");
2504 purple_conv_present_error(with, sip->account, msg_tmp);
2505 g_free(msg);
2506 g_free(msg_tmp);
2509 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2511 static gboolean
2512 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2514 gboolean ret = TRUE;
2515 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2516 struct sip_im_session * session = find_im_session(sip, with);
2517 struct sip_dialog *dialog;
2518 gchar *cseq;
2519 char *key;
2520 gchar *message;
2522 if (!session) {
2523 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2524 g_free(with);
2525 return FALSE;
2528 dialog = session->dialog;
2529 if (!dialog) {
2530 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2531 g_free(with);
2532 return FALSE;
2535 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2536 key = g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq));
2537 g_free(cseq);
2538 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2540 if (msg->response != 200) {
2541 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2543 sipe_present_message_undelivered_err(with, sip, message);
2544 im_session_destroy(sip, session);
2545 ret = FALSE;
2546 } else {
2547 g_hash_table_remove(session->unconfirmed_messages, key);
2548 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2549 key, g_hash_table_size(session->unconfirmed_messages));
2552 g_free(key);
2553 g_free(with);
2555 sipe_im_process_queue(sip, session);
2556 return ret;
2559 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2561 gchar *hdr;
2562 gchar *fullto;
2563 gchar *tmp;
2564 char *msgformat;
2565 char *msgtext;
2566 gchar *msgr_value;
2567 gchar *msgr;
2569 if (strncmp("sip:", session->with, 4)) {
2570 fullto = g_strdup_printf("sip:%s", session->with);
2571 } else {
2572 fullto = g_strdup(session->with);
2575 sipe_parse_html(msg, &msgformat, &msgtext);
2576 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2578 msgr_value = sipmsg_get_msgr_string(msgformat);
2579 g_free(msgformat);
2580 if (msgr_value) {
2581 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2582 g_free(msgr_value);
2583 } else {
2584 msgr = g_strdup("");
2587 tmp = get_contact(sip);
2588 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2589 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2590 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2591 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2592 tmp, msgr);
2593 g_free(tmp);
2594 g_free(msgr);
2596 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2597 g_free(msgtext);
2598 g_free(hdr);
2599 g_free(fullto);
2603 static void
2604 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2606 GSList *entry = session->outgoing_message_queue;
2608 if (session->outgoing_invite) return; //do not send messages until INVITE responded.
2610 while (entry) {
2611 char *key = g_strdup_printf("<%s><%d><MESSAGE>", session->dialog->callid, (session->dialog->cseq) + 1);
2612 char *queued_msg = entry->data;
2613 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2614 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2615 key, g_hash_table_size(session->unconfirmed_messages));
2616 g_free(key);
2617 sipe_send_message(sip, session, queued_msg);
2618 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2619 g_free(queued_msg);
2623 static void
2624 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2626 GSList *hdr = msg->headers;
2627 struct siphdrelement *elem;
2628 gchar *contact;
2630 while(hdr)
2632 elem = hdr->data;
2633 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2635 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2636 dialog->routes = g_slist_append(dialog->routes, route);
2638 hdr = g_slist_next(hdr);
2641 if (outgoing)
2643 dialog->routes = g_slist_reverse(dialog->routes);
2646 if (dialog->routes)
2648 dialog->request = dialog->routes->data;
2649 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2652 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2653 dialog->routes = g_slist_append(dialog->routes, contact);
2656 static void
2657 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2659 GSList *hdr = msg->headers;
2660 struct siphdrelement *elem;
2661 while(hdr)
2663 elem = hdr->data;
2664 if(!g_ascii_strcasecmp(elem->name, "Supported")
2665 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2667 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2670 hdr = g_slist_next(hdr);
2674 static void
2675 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2677 gchar *us = outgoing ? "From" : "To";
2678 gchar *them = outgoing ? "To" : "From";
2680 g_free(dialog->callid);
2681 g_free(dialog->ourtag);
2682 g_free(dialog->theirtag);
2684 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2685 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2686 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2687 if (!dialog->theirepid) {
2688 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2689 if (!dialog->theirepid) {
2690 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2694 sipe_get_route_header(msg, dialog, outgoing);
2695 sipe_get_supported_header(msg, dialog, outgoing);
2699 static gboolean
2700 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2702 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2703 struct sip_im_session * session = find_im_session(sip, with);
2704 struct sip_dialog *dialog;
2705 char *cseq;
2706 char *key;
2707 gchar *message;
2709 if (!session) {
2710 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2711 g_free(with);
2712 return FALSE;
2715 dialog = session->dialog;
2716 if (!dialog) {
2717 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2718 g_free(with);
2719 return FALSE;
2722 sipe_parse_dialog(msg, dialog, TRUE);
2724 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2725 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2726 g_free(cseq);
2727 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2729 if (msg->response != 200) {
2730 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2732 sipe_present_message_undelivered_err(with, sip, message);
2733 im_session_destroy(sip, session);
2734 g_free(with);
2735 return FALSE;
2738 dialog->cseq = 0;
2739 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2740 session->outgoing_invite = NULL;
2741 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2742 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2743 if (session->outgoing_message_queue) {
2744 char *queued_msg = session->outgoing_message_queue->data;
2745 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2746 g_free(queued_msg);
2750 sipe_im_process_queue(sip, session);
2752 g_hash_table_remove(session->unconfirmed_messages, key);
2753 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2754 key, g_hash_table_size(session->unconfirmed_messages));
2756 g_free(key);
2757 g_free(with);
2758 return TRUE;
2762 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session *session, const gchar *msg_body)
2764 gchar *hdr;
2765 gchar *to;
2766 gchar *from;
2767 gchar *contact;
2768 gchar *body;
2769 char *msgformat;
2770 char *msgtext;
2771 char *base64_msg;
2772 char *ms_text_format;
2773 gchar *msgr_value;
2774 gchar *msgr;
2775 char *key;
2777 if (session->dialog) {
2778 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2779 return;
2782 session->dialog = g_new0(struct sip_dialog, 1);
2783 session->dialog->callid = gencallid();
2785 if (strstr(session->with, "sip:")) {
2786 to = g_strdup(session->with);
2787 } else {
2788 to = g_strdup_printf("sip:%s", session->with);
2791 sipe_parse_html(msg_body, &msgformat, &msgtext);
2792 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2794 msgr_value = sipmsg_get_msgr_string(msgformat);
2795 g_free(msgformat);
2796 msgr = "";
2797 if (msgr_value) {
2798 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2799 g_free(msgr_value);
2802 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2803 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2804 g_free(msgtext);
2805 g_free(msgr);
2806 g_free(base64_msg);
2808 key = g_strdup_printf("<%s><%d><INVITE>", session->dialog->callid, (session->dialog->cseq) + 1);
2809 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2810 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2811 key, g_hash_table_size(session->unconfirmed_messages));
2812 g_free(key);
2814 contact = get_contact(sip);
2815 /* from = g_strdup_printf("sip:%s", sip->username);*/
2816 hdr = g_strdup_printf(
2817 /*"Supported: ms-delayed-accept\r\n"*/
2818 /*"Roster-Manager: <%s>\r\n"*/
2819 /*"EndPoints: <%s>, <%s>\r\n"*/
2820 /*"Supported: com.microsoft.rtc-multiparty\r\n"*/
2821 "Contact: %s\r\n%s"
2822 "Content-Type: application/sdp\r\n",
2823 contact, ms_text_format);
2824 g_free(ms_text_format);
2826 body = g_strdup_printf(
2827 "v=0\r\n"
2828 "o=- 0 0 IN IP4 %s\r\n"
2829 "s=session\r\n"
2830 "c=IN IP4 %s\r\n"
2831 "t=0 0\r\n"
2832 "m=message %d sip null\r\n"
2833 "a=accept-types:text/plain text/html image/gif "
2834 "multipart/alternative application/im-iscomposing+xml\r\n",
2835 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2837 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2838 to, to, hdr, body, session->dialog, process_invite_response);
2840 g_free(to);
2841 /* g_free(from);*/
2842 g_free(body);
2843 g_free(hdr);
2844 g_free(contact);
2847 static void
2848 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2850 if (session) {
2851 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2852 im_session_destroy(sip, session);
2856 static void
2857 sipe_convo_closed(PurpleConnection * gc, const char *who)
2859 struct sipe_account_data *sip = gc->proto_data;
2861 purple_debug_info("sipe", "conversation with %s closed\n", who);
2862 im_session_close(sip, find_im_session(sip, who));
2865 static void
2866 im_session_close_all (struct sipe_account_data *sip)
2868 GSList *entry = sip->im_sessions;
2869 while (entry) {
2870 im_session_close (sip, entry->data);
2871 entry = sip->im_sessions;
2875 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2877 struct sipe_account_data *sip;
2878 gchar *to;
2879 struct sip_im_session *session;
2881 sip = gc->proto_data;
2882 to = g_strdup(who);
2884 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2886 session = find_or_create_im_session(sip, who);
2888 // Queue the message
2889 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
2891 if (session->dialog && session->dialog->callid) {
2892 sipe_im_process_queue(sip, session);
2893 } else if (!session->outgoing_invite) {
2894 // Need to send the INVITE to get the outgoing dialog setup
2895 sipe_invite(sip, session, what);
2898 g_free(to);
2899 return 1;
2902 /* End IM Session (INVITE and MESSAGE methods) */
2904 static unsigned int
2905 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2907 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2908 struct sip_im_session *session;
2910 if (state == PURPLE_NOT_TYPING)
2911 return 0;
2913 session = find_im_session(sip, who);
2915 if (session && session->dialog) {
2916 send_sip_request(gc, "INFO", who, who,
2917 "Content-Type: application/xml\r\n",
2918 SIPE_SEND_TYPING, session->dialog, NULL);
2920 return SIPE_TYPING_SEND_TIMEOUT;
2923 static gboolean resend_timeout(struct sipe_account_data *sip)
2925 GSList *tmp = sip->transactions;
2926 time_t currtime = time(NULL);
2927 while (tmp) {
2928 struct transaction *trans = tmp->data;
2929 tmp = tmp->next;
2930 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2931 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2932 /* TODO 408 */
2933 } else {
2934 if ((currtime - trans->time > 2) && trans->retries == 0) {
2935 trans->retries++;
2936 sendout_sipmsg(sip, trans->msg);
2940 return TRUE;
2943 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2945 /* register again when security token expires */
2946 /* we have to start a new authentication as the security token
2947 * is almost expired by sending a not signed REGISTER message */
2948 purple_debug_info("sipe", "do a full reauthentication\n");
2949 sipe_auth_free(&sip->registrar);
2950 sip->registerstatus = 0;
2951 do_register(sip);
2952 sip->reauthenticate_set = FALSE;
2955 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2957 gchar *from;
2958 gchar *contenttype;
2959 gboolean found = FALSE;
2961 from = parse_from(sipmsg_find_header(msg, "From"));
2963 if (!from) return;
2965 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2967 contenttype = sipmsg_find_header(msg, "Content-Type");
2968 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2970 gchar *html = get_html_message(contenttype, msg->body);
2971 serv_got_im(sip->gc, from, html, 0, time(NULL));
2972 g_free(html);
2973 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2974 found = TRUE;
2976 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2977 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2978 xmlnode *state;
2979 gchar *statedata;
2981 if (!isc) {
2982 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2983 return;
2986 state = xmlnode_get_child(isc, "state");
2988 if (!state) {
2989 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2990 xmlnode_free(isc);
2991 return;
2994 statedata = xmlnode_get_data(state);
2995 if (statedata) {
2996 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2997 else serv_got_typing_stopped(sip->gc, from);
2999 g_free(statedata);
3001 xmlnode_free(isc);
3002 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3003 found = TRUE;
3005 if (!found) {
3006 purple_debug_info("sipe", "got unknown mime-type");
3007 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3009 g_free(from);
3012 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3014 gchar *ms_text_format;
3015 gchar *from;
3016 gchar *body;
3017 struct sip_im_session *session;
3019 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3021 // Only accept text invitations
3022 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3023 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3024 return;
3027 from = parse_from(sipmsg_find_header(msg, "From"));
3028 session = find_or_create_im_session (sip, from);
3029 if (session) {
3030 if (session->dialog) {
3031 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3032 } else {
3033 session->dialog = g_new0(struct sip_dialog, 1);
3035 sipe_parse_dialog(msg, session->dialog, FALSE);
3037 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3038 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3039 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3040 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3042 } else {
3043 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3046 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3047 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3048 if (ms_text_format) {
3049 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3051 gchar *html = get_html_message(ms_text_format, NULL);
3052 if (html) {
3053 serv_got_im(sip->gc, from, html, 0, time(NULL));
3054 g_free(html);
3055 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3059 g_free(from);
3061 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3062 sipmsg_remove_header(msg, "Ms-Text-Format");
3063 sipmsg_remove_header(msg, "EndPoints");
3064 sipmsg_remove_header(msg, "User-Agent");
3065 sipmsg_remove_header(msg, "Roster-Manager");
3067 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3068 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3070 body = g_strdup_printf(
3071 "v=0\r\n"
3072 "o=- 0 0 IN IP4 %s\r\n"
3073 "s=session\r\n"
3074 "c=IN IP4 %s\r\n"
3075 "t=0 0\r\n"
3076 "m=message %d sip sip:%s\r\n"
3077 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3078 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3079 sip->realport, sip->username);
3080 send_sip_response(sip->gc, msg, 200, "OK", body);
3081 g_free(body);
3084 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3086 gchar *body;
3088 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3089 sipmsg_remove_header(msg, "EndPoints");
3090 sipmsg_remove_header(msg, "User-Agent");
3092 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3093 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3095 body = g_strdup_printf(
3096 "v=0\r\n"
3097 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3098 "s=session\r\n"
3099 "c=IN IP4 0.0.0.0\r\n"
3100 "t=0 0\r\n"
3101 "m=message %d sip sip:%s\r\n"
3102 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3103 sip->realport, sip->username);
3104 send_sip_response(sip->gc, msg, 200, "OK", body);
3105 g_free(body);
3108 static void sipe_connection_cleanup(struct sipe_account_data *);
3109 static void create_connection(struct sipe_account_data *, gchar *, int);
3111 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3113 gchar *tmp;
3114 const gchar *expires_header;
3115 int expires, i;
3116 GSList *hdr = msg->headers;
3117 GSList *entry;
3118 struct siphdrelement *elem;
3120 expires_header = sipmsg_find_header(msg, "Expires");
3121 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3122 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3124 switch (msg->response) {
3125 case 200:
3126 if (expires == 0) {
3127 sip->registerstatus = 0;
3128 } else {
3129 gchar *contact_hdr = NULL;
3130 gchar *gruu = NULL;
3131 gchar *epid;
3132 gchar *uuid;
3133 gchar *timeout;
3135 if (!sip->reregister_set) {
3136 gchar *action_name = g_strdup_printf("<%s>", "registration");
3137 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
3138 g_free(action_name);
3139 sip->reregister_set = TRUE;
3142 sip->registerstatus = 3;
3144 if (!sip->reauthenticate_set) {
3145 /* we have to reauthenticate as our security token expires
3146 after eight hours (be five minutes early) */
3147 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3148 guint reauth_timeout = (8 * 3600) - 360;
3149 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3150 g_free(action_name);
3151 sip->reauthenticate_set = TRUE;
3154 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3156 epid = get_epid(sip);
3157 uuid = generateUUIDfromEPID(epid);
3158 g_free(epid);
3160 // There can be multiple Contact headers (one per location where the user is logged in) so
3161 // make sure to only get the one for this uuid
3162 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3163 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3164 if (valid_contact) {
3165 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3166 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3167 g_free(valid_contact);
3168 break;
3169 } else {
3170 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3173 g_free(uuid);
3175 g_free(sip->contact);
3176 if(gruu) {
3177 sip->contact = g_strdup_printf("<%s>", gruu);
3178 g_free(gruu);
3179 } else {
3180 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3181 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);
3183 sip->msrtc_event_categories = FALSE;
3184 sip->batched_support = FALSE;
3186 while(hdr)
3188 elem = hdr->data;
3189 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3190 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3191 sip->msrtc_event_categories = TRUE;
3192 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3194 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3195 sip->batched_support = TRUE;
3196 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3199 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3200 gchar **caps = g_strsplit(elem->value,",",0);
3201 i = 0;
3202 while (caps[i]) {
3203 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3204 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3205 i++;
3207 g_strfreev(caps);
3209 hdr = g_slist_next(hdr);
3212 if (!sip->subscribed) { //do it just once, not every re-register
3213 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3214 sipe_options_request(sip, sip->sipdomain);
3216 entry = sip->allow_events;
3217 while (entry) {
3218 tmp = entry->data;
3219 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3220 sipe_subscribe_roaming_contacts(sip, msg);
3222 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3223 sipe_subscribe_roaming_acl(sip, msg);
3225 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3226 sipe_subscribe_roaming_self(sip, msg);
3228 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3229 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3230 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3231 sipe_subscribe_roaming_provisioning(sip, msg);
3233 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3234 sipe_subscribe_presence_wpending(sip, msg);
3236 entry = entry->next;
3238 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3239 sip->subscribed = TRUE;
3242 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3243 "timeout=", ";", NULL);
3244 if (timeout != NULL) {
3245 sscanf(timeout, "%u", &sip->keepalive_timeout);
3246 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3247 sip->keepalive_timeout);
3248 g_free(timeout);
3249 } else {
3250 sip->keepalive_timeout = 300;
3253 // Should we remove the transaction here?
3254 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3255 transactions_remove(sip, tc);
3257 break;
3258 case 301:
3260 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3262 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3263 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3264 gchar **tmp;
3265 gchar *hostname;
3266 int port = 0;
3267 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3268 int i = 1;
3270 tmp = g_strsplit(parts[0], ":", 0);
3271 hostname = g_strdup(tmp[0]);
3272 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3273 g_strfreev(tmp);
3275 while (parts[i]) {
3276 tmp = g_strsplit(parts[i], "=", 0);
3277 if (tmp[1]) {
3278 if (g_strcasecmp("transport", tmp[0]) == 0) {
3279 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3280 transport = SIPE_TRANSPORT_TCP;
3281 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3282 transport = SIPE_TRANSPORT_UDP;
3286 g_strfreev(tmp);
3287 i++;
3289 g_strfreev(parts);
3291 /* Close old connection */
3292 sipe_connection_cleanup(sip);
3294 /* Create new connection */
3295 sip->transport = transport;
3296 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3297 hostname, port, TRANSPORT_DESCRIPTOR);
3298 create_connection(sip, hostname, port);
3300 g_free(redirect);
3302 break;
3303 case 401:
3304 if (sip->registerstatus != 2) {
3305 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3306 if (sip->registrar.retries > 3) {
3307 sip->gc->wants_to_die = TRUE;
3308 purple_connection_error(sip->gc, _("Wrong Password"));
3309 return TRUE;
3311 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3312 tmp = sipmsg_find_auth_header(msg, "NTLM");
3313 } else {
3314 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3316 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3317 fill_auth(sip, tmp, &sip->registrar);
3318 sip->registerstatus = 2;
3319 if (sip->account->disconnecting) {
3320 do_register_exp(sip, 0);
3321 } else {
3322 do_register(sip);
3325 break;
3326 case 403:
3328 gchar *warning = sipmsg_find_header(msg, "Warning");
3329 if (warning != NULL) {
3330 /* Example header:
3331 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3333 gchar **tmp = g_strsplit(warning, "\"", 0);
3334 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3335 g_strfreev(tmp);
3336 } else {
3337 warning = g_strdup(_("You have been rejected by the server"));
3340 sip->gc->wants_to_die = TRUE;
3341 purple_connection_error(sip->gc, warning);
3342 g_free(warning);
3343 return TRUE;
3345 break;
3346 case 404:
3348 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3349 if (warning != NULL) {
3350 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3351 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3352 g_free(reason);
3353 } else {
3354 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3357 sip->gc->wants_to_die = TRUE;
3358 purple_connection_error(sip->gc, warning);
3359 g_free(warning);
3360 return TRUE;
3362 break;
3363 case 503:
3365 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3366 if (warning != NULL) {
3367 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3368 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3369 g_free(reason);
3370 } else {
3371 warning = g_strdup(_("Service unavailable: no reason given"));
3374 sip->gc->wants_to_die = TRUE;
3375 purple_connection_error(sip->gc, warning);
3376 g_free(warning);
3377 return TRUE;
3379 break;
3381 return TRUE;
3384 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3386 const char *uri;
3387 xmlnode *xn_categories;
3388 xmlnode *xn_category;
3389 xmlnode *xn_node;
3390 const char *activity = NULL;
3392 xn_categories = xmlnode_from_str(data, len);
3393 uri = xmlnode_get_attrib(xn_categories, "uri");
3395 for (xn_category = xmlnode_get_child(xn_categories, "category");
3396 xn_category ;
3397 xn_category = xmlnode_get_next_twin(xn_category) )
3399 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3401 if (!strcmp(attrVar, "note"))
3403 char *note;
3404 struct sipe_buddy *sbuddy;
3405 xn_node = xmlnode_get_child(xn_category, "note");
3406 if (!xn_node) continue;
3407 xn_node = xmlnode_get_child(xn_node, "body");
3408 if (!xn_node) continue;
3410 note = xmlnode_get_data(xn_node);
3412 if(uri){
3413 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3415 if (sbuddy && note)
3417 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note);
3418 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3419 sbuddy->annotation = g_strdup(note);
3421 if(note)
3422 g_free(note);
3424 else if(!strcmp(attrVar, "state"))
3426 char *data;
3427 int avail;
3428 xn_node = xmlnode_get_child(xn_category, "state");
3429 if (!xn_node) continue;
3430 xn_node = xmlnode_get_child(xn_node, "availability");
3431 if (!xn_node) continue;
3433 data = xmlnode_get_data(xn_node);
3434 avail = atoi(data);
3435 g_free(data);
3437 if (avail < 3000)
3438 activity = SIPE_STATUS_ID_UNKNOWN;
3439 else if (avail < 4500)
3440 activity = SIPE_STATUS_ID_AVAILABLE;
3441 else if (avail < 6000)
3442 activity = SIPE_STATUS_ID_BRB;
3443 else if (avail < 7500)
3444 activity = SIPE_STATUS_ID_ONPHONE;
3445 else if (avail < 9000)
3446 activity = SIPE_STATUS_ID_BUSY;
3447 else if (avail < 12000)
3448 activity = SIPE_STATUS_ID_DND;
3449 else if (avail < 18000)
3450 activity = SIPE_STATUS_ID_AWAY;
3451 else
3452 activity = SIPE_STATUS_ID_OFFLINE;
3455 if(activity) {
3456 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
3457 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3460 xmlnode_free(xn_categories);
3463 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3465 const char *uri,*state;
3466 xmlnode *xn_list;
3467 xmlnode *xn_resource;
3468 xmlnode *xn_instance;
3470 xn_list = xmlnode_from_str(data, len);
3472 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3473 xn_resource;
3474 xn_resource = xmlnode_get_next_twin(xn_resource) )
3476 struct sipe_buddy *sbuddy;
3477 uri = xmlnode_get_attrib(xn_resource, "uri");
3478 xn_instance = xmlnode_get_child(xn_resource, "instance");
3479 if (!xn_instance) return;
3481 state = xmlnode_get_attrib(xn_instance, "state");
3482 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3483 if(strstr(state,"resubscribe")){
3484 sipe_subscribe_presence_single(sip, uri);
3485 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3486 if(sbuddy){
3487 sbuddy->resubscribed = TRUE;
3494 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3496 const gchar *uri;
3497 gchar *getbasic;
3498 gchar *activity = NULL;
3499 xmlnode *pidf;
3500 xmlnode *basicstatus = NULL, *tuple, *status;
3501 gboolean isonline = FALSE;
3502 xmlnode *display_name_node;
3504 pidf = xmlnode_from_str(data, len);
3505 if (!pidf) {
3506 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3507 return;
3510 uri = xmlnode_get_attrib(pidf, "entity");
3512 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3514 if ((status = xmlnode_get_child(tuple, "status"))) {
3515 basicstatus = xmlnode_get_child(status, "basic");
3519 if (!basicstatus) {
3520 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3521 xmlnode_free(pidf);
3522 return;
3525 getbasic = xmlnode_get_data(basicstatus);
3526 if (!getbasic) {
3527 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3528 xmlnode_free(pidf);
3529 return;
3532 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3533 if (strstr(getbasic, "open")) {
3534 isonline = TRUE;
3536 g_free(getbasic);
3538 display_name_node = xmlnode_get_child(pidf, "display-name");
3539 // updating display name if alias was just URI
3540 if (display_name_node) {
3541 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3542 GSList *entry = buddies;
3543 PurpleBuddy *p_buddy;
3544 char * display_name = xmlnode_get_data(display_name_node);
3546 while (entry) {
3547 const char *server_alias;
3548 char *alias;
3550 p_buddy = entry->data;
3552 alias = (char *)purple_buddy_get_alias(p_buddy);
3553 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3554 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3555 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3556 purple_blist_alias_buddy(p_buddy, display_name);
3558 g_free(alias);
3560 server_alias = purple_buddy_get_server_alias(p_buddy);
3561 if (display_name &&
3562 ( (server_alias && strcmp(display_name, server_alias))
3563 || !server_alias || strlen(server_alias) == 0 )
3565 purple_blist_server_alias_buddy(p_buddy, display_name);
3568 entry = entry->next;
3570 g_free(display_name);
3573 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3574 if ((status = xmlnode_get_child(tuple, "status"))) {
3575 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3576 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3577 activity = xmlnode_get_data(basicstatus);
3578 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3584 if (isonline) {
3585 const gchar * status_id = NULL;
3586 if (activity) {
3587 if (strstr(activity, "busy")) {
3588 status_id = SIPE_STATUS_ID_BUSY;
3589 } else if (strstr(activity, "away")) {
3590 status_id = SIPE_STATUS_ID_AWAY;
3594 if (!status_id) {
3595 status_id = SIPE_STATUS_ID_AVAILABLE;
3598 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3599 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3600 } else {
3601 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
3604 g_free(activity);
3605 xmlnode_free(pidf);
3608 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3610 const char *availability;
3611 const char *activity;
3612 const char *display_name = NULL;
3613 const char *activity_name = NULL;
3614 const char *name;
3615 char *uri;
3616 int avl;
3617 int act;
3618 struct sipe_buddy *sbuddy;
3620 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3622 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3623 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3624 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3625 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3626 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3627 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3628 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
3629 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
3631 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3632 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3633 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3634 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3635 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3636 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3638 name = xmlnode_get_attrib(xn_presentity, "uri");
3639 uri = g_strdup_printf("sip:%s", name);
3640 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3641 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3643 // updating display name if alias was just URI
3644 if (xn_display_name) {
3645 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3646 GSList *entry = buddies;
3647 PurpleBuddy *p_buddy;
3648 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3650 while (entry) {
3651 const char *email_str, *server_alias;
3653 p_buddy = entry->data;
3655 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3656 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3657 purple_blist_alias_buddy(p_buddy, display_name);
3660 server_alias = purple_buddy_get_server_alias(p_buddy);
3661 if (display_name &&
3662 ( (server_alias && strcmp(display_name, server_alias))
3663 || !server_alias || strlen(server_alias) == 0 )
3665 purple_blist_server_alias_buddy(p_buddy, display_name);
3668 if (email) {
3669 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3670 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3671 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3675 entry = entry->next;
3679 avl = atoi(availability);
3680 act = atoi(activity);
3682 if(sip->msrtc_event_categories){
3683 if (act == 100 && avl == 0)
3684 activity_name = SIPE_STATUS_ID_OFFLINE;
3685 else if (act == 100 && avl == 300)
3686 activity_name = SIPE_STATUS_ID_AWAY;
3687 else if (act == 300 && avl == 300)
3688 activity_name = SIPE_STATUS_ID_BRB;
3689 else if (act == 400 && avl == 300)
3690 activity_name = SIPE_STATUS_ID_AVAILABLE;
3691 else if (act == 500 && act == 300)
3692 activity_name = SIPE_STATUS_ID_ONPHONE;
3693 else if (act == 600 && avl == 300)
3694 activity_name = SIPE_STATUS_ID_BUSY;
3695 else if (act == 0 && avl == 0){ //MSRTC elements are zero
3696 if(avail){ //Check for LegacyInterop elements
3697 avl = atoi(avail);
3698 if(avl == 18500)
3699 activity_name = SIPE_STATUS_ID_OFFLINE;
3700 else if (avl == 3500)
3701 activity_name = SIPE_STATUS_ID_AVAILABLE;
3702 else if (avl == 15500)
3703 activity_name = SIPE_STATUS_ID_AWAY;
3704 else if (avl == 6500)
3705 activity_name = SIPE_STATUS_ID_BUSY;
3706 else if (avl == 12500)
3707 activity_name = SIPE_STATUS_ID_BRB;
3712 if(activity_name == NULL){
3713 if (act <= 100)
3714 activity_name = SIPE_STATUS_ID_AWAY;
3715 else if (act <= 150)
3716 activity_name = SIPE_STATUS_ID_LUNCH;
3717 else if (act <= 300)
3718 activity_name = SIPE_STATUS_ID_BRB;
3719 else if (act <= 400)
3720 activity_name = SIPE_STATUS_ID_AVAILABLE;
3721 else if (act <= 500)
3722 activity_name = SIPE_STATUS_ID_ONPHONE;
3723 else if (act <= 600)
3724 activity_name = SIPE_STATUS_ID_BUSY;
3725 else
3726 activity_name = SIPE_STATUS_ID_AVAILABLE;
3728 if (avl == 0)
3729 activity_name = SIPE_STATUS_ID_OFFLINE;
3732 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3733 if (sbuddy)
3735 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3736 sbuddy->annotation = NULL;
3737 if (note) { sbuddy->annotation = g_strdup(note); }
3739 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3740 sbuddy->device_name = NULL;
3741 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3744 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3745 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3746 g_free(note);
3747 xmlnode_free(xn_presentity);
3748 g_free(uri);
3751 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3753 char *ctype = sipmsg_find_header(msg, "Content-Type");
3755 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3757 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3758 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3760 const char *content = msg->body;
3761 unsigned length = msg->bodylen;
3762 PurpleMimeDocument *mime = NULL;
3764 if (strstr(ctype, "multipart"))
3766 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3767 const char *content_type;
3768 GList* parts;
3769 mime = purple_mime_document_parse(doc);
3770 parts = purple_mime_document_get_parts(mime);
3771 while(parts) {
3772 content = purple_mime_part_get_data(parts->data);
3773 length = purple_mime_part_get_length(parts->data);
3774 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3775 if(content_type && strstr(content_type,"application/rlmi+xml"))
3777 process_incoming_notify_rlmi_resub(sip, content, length);
3779 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3781 process_incoming_notify_msrtc(sip, content, length);
3783 else
3785 process_incoming_notify_rlmi(sip, content, length);
3787 parts = parts->next;
3789 g_free(doc);
3791 if (mime)
3793 purple_mime_document_free(mime);
3796 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3798 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3800 else if(strstr(ctype, "application/rlmi+xml"))
3802 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3805 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3807 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3809 else
3811 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3816 * Dispatcher for all incoming subscription information
3817 * whether it comes from NOTIFY, BENOTIFY requests or
3818 * piggy-backed to subscription's OK responce.
3820 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3821 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3823 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3825 gchar *event = sipmsg_find_header(msg, "Event");
3826 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3827 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3828 int timeout = 0;
3830 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3831 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3833 if (!request)
3835 const gchar *expires_header;
3836 expires_header = sipmsg_find_header(msg, "Expires");
3837 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3838 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
3839 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
3842 if (!subscription_state || strstr(subscription_state, "active"))
3844 if (event && !g_ascii_strcasecmp(event, "presence"))
3846 sipe_process_presence(sip, msg);
3848 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3850 sipe_process_roaming_contacts(sip, msg, NULL);
3852 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
3854 sipe_process_roaming_self(sip, msg);
3856 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3858 sipe_process_roaming_acl(sip, msg);
3860 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3862 sipe_process_presence_wpending(sip, msg);
3864 else
3866 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3870 //The server sends a (BE)NOTIFY with the status 'terminated'
3871 if(request && subscription_state && strstr(subscription_state, "terminated") ) {
3872 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3873 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3874 g_free(from);
3877 if (timeout && event) {// For LSC 2005 and OCS 2007
3878 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
3879 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
3881 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
3882 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_roaming_contacts, sip, msg);
3883 g_free(action_name);
3885 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
3886 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
3888 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
3889 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_roaming_acl, sip, msg);
3890 g_free(action_name);
3892 else*/
3893 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
3894 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3896 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3897 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_wpending, sip, NULL);
3898 g_free(action_name);
3900 else if (!g_ascii_strcasecmp(event, "presence") &&
3901 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3903 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", who);
3904 if(sip->batched_support){
3905 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
3906 if(!g_ascii_strcasecmp(who, my_self)){
3907 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_batched, sip,NULL);
3908 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
3910 else{
3911 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_single, sip, who);
3912 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who,timeout);
3914 g_free(my_self);
3916 else{
3917 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_single, sip, who);
3918 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
3920 g_free(action_name);
3924 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
3926 sipe_process_registration_notify(sip, msg);
3929 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3930 if (request && !benotify)
3932 sipmsg_remove_header(msg, "Expires");
3933 sipmsg_remove_header(msg, "subscription-state");
3934 sipmsg_remove_header(msg, "Event");
3935 sipmsg_remove_header(msg, "Require");
3936 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3941 * unused. Needed?
3943 static gchar* gen_xpidf(struct sipe_account_data *sip)
3945 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3946 "<presence>\r\n"
3947 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3948 "<display name=\"sip:%s\"/>\r\n"
3949 "<atom id=\"1234\">\r\n"
3950 "<address uri=\"sip:%s\">\r\n"
3951 "<status status=\"%s\"/>\r\n"
3952 "</address>\r\n"
3953 "</atom>\r\n"
3954 "</presence>\r\n",
3955 sip->username,
3956 sip->username,
3957 sip->username,
3958 sip->status);
3959 return doc;
3964 static gchar* gen_pidf(struct sipe_account_data *sip)
3966 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3967 "<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"
3968 "<tuple id=\"0\">\r\n"
3969 "<status>\r\n"
3970 "<basic>open</basic>\r\n"
3971 "<ep:activities>\r\n"
3972 " <ep:activity>%s</ep:activity>\r\n"
3973 "</ep:activities>"
3974 "</status>\r\n"
3975 "</tuple>\r\n"
3976 "<ci:display-name>%s</ci:display-name>\r\n"
3977 "</presence>",
3978 sip->username,
3979 sip->status,
3980 sip->username);
3981 return doc;
3985 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
3987 int availability = 300; // online
3988 int activity = 400; // Available
3989 gchar *name;
3990 gchar *body;
3991 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
3992 activity = 100;
3993 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
3994 activity = 150;
3995 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
3996 activity = 300;
3997 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
3998 activity = 400;
3999 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4000 activity = 500;
4001 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4002 activity = 600;
4003 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4004 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4005 availability = 0; // offline
4006 activity = 100;
4007 } else {
4008 activity = 400; // available
4011 name = g_strdup_printf("sip: sip:%s", sip->username);
4012 //@TODO: send user data - state; add hostname in upper case
4013 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4014 send_soap_request_with_cb(sip, body, NULL , NULL);
4015 g_free(name);
4016 g_free(body);
4019 static gboolean
4020 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4022 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4023 if (msg->response == 200) {
4024 sip->status_version = 0;
4025 send_presence_status(sip);
4027 return TRUE;
4030 static gboolean
4031 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4033 if (msg->response == 409) {
4034 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4035 // TODO need to parse the version #'s?
4036 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4037 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4038 gchar *tmp;
4039 gchar *hdr;
4041 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4043 tmp = get_contact(sip);
4044 hdr = g_strdup_printf("Contact: %s\r\n"
4045 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4047 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4049 g_free(tmp);
4050 g_free(hdr);
4051 g_free(uri);
4052 g_free(doc);
4054 return TRUE;
4057 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4059 int code;
4060 gchar *uri;
4061 gchar *doc;
4062 gchar *tmp;
4063 gchar *hdr;
4064 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4065 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4066 code = 12000;
4067 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4068 code = 9000;
4069 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4070 code = 7500;
4071 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4072 code = 6000;
4073 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4074 code = 4500;
4075 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4076 code = 3000;
4077 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4078 code = 0;
4079 } else {
4080 // Offline or invisible
4081 code = 18000;
4084 uri = g_strdup_printf("sip:%s", sip->username);
4085 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4086 sip->status_version, code,
4087 sip->status_version, code,
4088 sip->status_version, note ? note : "",
4089 sip->status_version, note ? note : "",
4090 sip->status_version, note ? note : ""
4092 sip->status_version++;
4094 tmp = get_contact(sip);
4095 hdr = g_strdup_printf("Contact: %s\r\n"
4096 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4098 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4100 g_free(tmp);
4101 g_free(hdr);
4102 g_free(uri);
4103 g_free(doc);
4106 static void send_presence_status(struct sipe_account_data *sip)
4108 PurpleStatus * status = purple_account_get_active_status(sip->account);
4109 const gchar *note;
4110 if (!status) return;
4112 note = purple_status_get_attr_string(status, "message");
4114 if(sip->msrtc_event_categories){
4115 send_presence_category_publish(sip, note);
4116 } else {
4117 send_presence_soap(sip, note);
4121 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4123 gboolean found = FALSE;
4124 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4125 if (msg->response == 0) { /* request */
4126 if (!strcmp(msg->method, "MESSAGE")) {
4127 process_incoming_message(sip, msg);
4128 found = TRUE;
4129 } else if (!strcmp(msg->method, "NOTIFY")) {
4130 purple_debug_info("sipe","send->process_incoming_notify\n");
4131 process_incoming_notify(sip, msg, TRUE, FALSE);
4132 found = TRUE;
4133 } else if (!strcmp(msg->method, "BENOTIFY")) {
4134 purple_debug_info("sipe","send->process_incoming_benotify\n");
4135 process_incoming_notify(sip, msg, TRUE, TRUE);
4136 found = TRUE;
4137 } else if (!strcmp(msg->method, "INVITE")) {
4138 process_incoming_invite(sip, msg);
4139 found = TRUE;
4140 } else if (!strcmp(msg->method, "OPTIONS")) {
4141 process_incoming_options(sip, msg);
4142 found = TRUE;
4143 } else if (!strcmp(msg->method, "INFO")) {
4144 // TODO needs work
4145 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4146 if (from) {
4147 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4149 g_free(from);
4150 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4151 found = TRUE;
4152 } else if (!strcmp(msg->method, "ACK")) {
4153 // ACK's don't need any response
4154 found = TRUE;
4155 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4156 // LCS 2005 sends us these - just respond 200 OK
4157 found = TRUE;
4158 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4159 } else if (!strcmp(msg->method, "BYE")) {
4160 struct sip_im_session *session;
4161 gchar *from;
4162 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4164 from = parse_from(sipmsg_find_header(msg, "From"));
4165 session = find_im_session (sip, from);
4166 g_free(from);
4168 if (session) {
4169 // TODO Let the user know the other user left the conversation?
4170 im_session_destroy(sip, session);
4173 found = TRUE;
4174 } else {
4175 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4177 } else { /* response */
4178 struct transaction *trans = transactions_find(sip, msg);
4179 if (trans) {
4180 if (msg->response == 407) {
4181 gchar *resend, *auth, *ptmp;
4183 if (sip->proxy.retries > 30) return;
4184 sip->proxy.retries++;
4185 /* do proxy authentication */
4187 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4189 fill_auth(sip, ptmp, &sip->proxy);
4190 auth = auth_header(sip, &sip->proxy, trans->msg);
4191 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4192 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4193 g_free(auth);
4194 resend = sipmsg_to_string(trans->msg);
4195 /* resend request */
4196 sendout_pkt(sip->gc, resend);
4197 g_free(resend);
4198 } else {
4199 if (msg->response == 100 || msg->response == 180) {
4200 /* ignore provisional response */
4201 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4202 } else {
4203 sip->proxy.retries = 0;
4204 if (!strcmp(trans->msg->method, "REGISTER")) {
4205 if (msg->response == 401)
4207 sip->registrar.retries++;
4208 sip->registrar.expires = 0;
4210 else
4212 sip->registrar.retries = 0;
4214 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4215 } else {
4216 if (msg->response == 401) {
4217 gchar *resend, *auth, *ptmp;
4219 if (sip->registrar.retries > 4) return;
4220 sip->registrar.retries++;
4222 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4223 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4224 } else {
4225 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4228 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4230 fill_auth(sip, ptmp, &sip->registrar);
4231 auth = auth_header(sip, &sip->registrar, trans->msg);
4232 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4233 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
4235 //sipmsg_remove_header(trans->msg, "Authorization");
4236 //sipmsg_add_header(trans->msg, "Authorization", auth);
4237 g_free(auth);
4238 resend = sipmsg_to_string(trans->msg);
4239 /* resend request */
4240 sendout_pkt(sip->gc, resend);
4241 g_free(resend);
4245 if (trans->callback) {
4246 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4247 /* call the callback to process response*/
4248 (trans->callback)(sip, msg, trans);
4250 /* Not sure if this is needed or what needs to be done
4251 but transactions seem to be removed prematurely so
4252 this only removes them if the response is 200 OK */
4253 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4254 /*Has a bug and it's unneccesary*/
4255 /*transactions_remove(sip, trans);*/
4259 found = TRUE;
4260 } else {
4261 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4264 if (!found) {
4265 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4269 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4271 char *cur;
4272 char *dummy;
4273 struct sipmsg *msg;
4274 int restlen;
4275 cur = conn->inbuf;
4277 /* according to the RFC remove CRLF at the beginning */
4278 while (*cur == '\r' || *cur == '\n') {
4279 cur++;
4281 if (cur != conn->inbuf) {
4282 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4283 conn->inbufused = strlen(conn->inbuf);
4286 /* Received a full Header? */
4287 sip->processing_input = TRUE;
4288 while (sip->processing_input &&
4289 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4290 time_t currtime = time(NULL);
4291 cur += 2;
4292 cur[0] = '\0';
4293 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4294 msg = sipmsg_parse_header(conn->inbuf);
4295 cur[0] = '\r';
4296 cur += 2;
4297 restlen = conn->inbufused - (cur - conn->inbuf);
4298 if (restlen >= msg->bodylen) {
4299 dummy = g_malloc(msg->bodylen + 1);
4300 memcpy(dummy, cur, msg->bodylen);
4301 dummy[msg->bodylen] = '\0';
4302 msg->body = dummy;
4303 cur += msg->bodylen;
4304 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4305 conn->inbufused = strlen(conn->inbuf);
4306 } else {
4307 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4308 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4309 sipmsg_free(msg);
4310 return;
4313 /*if (msg->body) {
4314 purple_debug_info("sipe", "body:\n%s", msg->body);
4317 // Verify the signature before processing it
4318 if (sip->registrar.ntlm_key) {
4319 struct sipmsg_breakdown msgbd;
4320 gchar *signature_input_str;
4321 gchar *signature = NULL;
4322 gchar *rspauth;
4323 msgbd.msg = msg;
4324 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4325 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4326 if (signature_input_str != NULL) {
4327 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4329 g_free(signature_input_str);
4331 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4333 if (signature != NULL) {
4334 if (rspauth != NULL) {
4335 if (purple_ntlm_verify_signature (signature, rspauth)) {
4336 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4337 process_input_message(sip, msg);
4338 } else {
4339 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4340 purple_connection_error(sip->gc, _("Invalid message signature received"));
4341 sip->gc->wants_to_die = TRUE;
4343 } else if (msg->response == 401) {
4344 purple_connection_error(sip->gc, _("Wrong Password"));
4345 sip->gc->wants_to_die = TRUE;
4347 g_free(signature);
4350 g_free(rspauth);
4351 sipmsg_breakdown_free(&msgbd);
4352 } else {
4353 process_input_message(sip, msg);
4356 sipmsg_free(msg);
4360 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4362 PurpleConnection *gc = data;
4363 struct sipe_account_data *sip = gc->proto_data;
4364 struct sipmsg *msg;
4365 int len;
4366 time_t currtime;
4368 static char buffer[65536];
4369 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4370 buffer[len] = '\0';
4371 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4372 msg = sipmsg_parse_msg(buffer);
4373 if (msg) process_input_message(sip, msg);
4377 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4379 struct sipe_account_data *sip = gc->proto_data;
4380 PurpleSslConnection *gsc = sip->gsc;
4382 purple_debug_error("sipe", "%s",debug);
4383 purple_connection_error(gc, msg);
4385 /* Invalidate this connection. Next send will open a new one */
4386 if (gsc) {
4387 connection_remove(sip, gsc->fd);
4388 purple_ssl_close(gsc);
4390 sip->gsc = NULL;
4391 sip->fd = -1;
4394 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4396 PurpleConnection *gc = data;
4397 struct sipe_account_data *sip;
4398 struct sip_connection *conn;
4399 int readlen, len;
4400 gboolean firstread = TRUE;
4402 /* NOTE: This check *IS* necessary */
4403 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4404 purple_ssl_close(gsc);
4405 return;
4408 sip = gc->proto_data;
4409 conn = connection_find(sip, gsc->fd);
4410 if (conn == NULL) {
4411 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4412 gc->wants_to_die = TRUE;
4413 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4414 return;
4417 /* Read all available data from the SSL connection */
4418 do {
4419 /* Increase input buffer size as needed */
4420 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4421 conn->inbuflen += SIMPLE_BUF_INC;
4422 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4423 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4426 /* Try to read as much as there is space left in the buffer */
4427 readlen = conn->inbuflen - conn->inbufused - 1;
4428 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4430 if (len < 0 && errno == EAGAIN) {
4431 /* Try again later */
4432 return;
4433 } else if (len < 0) {
4434 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4435 return;
4436 } else if (firstread && (len == 0)) {
4437 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4438 return;
4441 conn->inbufused += len;
4442 firstread = FALSE;
4444 /* Equivalence indicates that there is possibly more data to read */
4445 } while (len == readlen);
4447 conn->inbuf[conn->inbufused] = '\0';
4448 process_input(sip, conn);
4452 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4454 PurpleConnection *gc = data;
4455 struct sipe_account_data *sip = gc->proto_data;
4456 int len;
4457 struct sip_connection *conn = connection_find(sip, source);
4458 if (!conn) {
4459 purple_debug_error("sipe", "Connection not found!\n");
4460 return;
4463 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4464 conn->inbuflen += SIMPLE_BUF_INC;
4465 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4468 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4470 if (len < 0 && errno == EAGAIN)
4471 return;
4472 else if (len <= 0) {
4473 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4474 connection_remove(sip, source);
4475 if (sip->fd == source) sip->fd = -1;
4476 return;
4479 conn->inbufused += len;
4480 conn->inbuf[conn->inbufused] = '\0';
4482 process_input(sip, conn);
4485 /* Callback for new connections on incoming TCP port */
4486 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4488 PurpleConnection *gc = data;
4489 struct sipe_account_data *sip = gc->proto_data;
4490 struct sip_connection *conn;
4492 int newfd = accept(source, NULL, NULL);
4494 conn = connection_create(sip, newfd);
4496 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4499 static void login_cb(gpointer data, gint source, const gchar *error_message)
4501 PurpleConnection *gc = data;
4502 struct sipe_account_data *sip;
4503 struct sip_connection *conn;
4505 if (!PURPLE_CONNECTION_IS_VALID(gc))
4507 if (source >= 0)
4508 close(source);
4509 return;
4512 if (source < 0) {
4513 purple_connection_error(gc, _("Could not connect"));
4514 return;
4517 sip = gc->proto_data;
4518 sip->fd = source;
4519 sip->last_keepalive = time(NULL);
4521 conn = connection_create(sip, source);
4523 do_register(sip);
4525 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4528 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4530 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4531 if (sip == NULL) return;
4533 do_register(sip);
4536 static guint sipe_ht_hash_nick(const char *nick)
4538 char *lc = g_utf8_strdown(nick, -1);
4539 guint bucket = g_str_hash(lc);
4540 g_free(lc);
4542 return bucket;
4545 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4547 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4550 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4552 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4554 sip->listen_data = NULL;
4556 if (listenfd == -1) {
4557 purple_connection_error(sip->gc, _("Could not create listen socket"));
4558 return;
4561 sip->fd = listenfd;
4563 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4564 sip->listenfd = sip->fd;
4566 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4568 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4569 do_register(sip);
4572 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4574 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4575 int addr_size;
4577 sip->query_data = NULL;
4579 if (!hosts || !hosts->data) {
4580 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4581 return;
4584 addr_size = GPOINTER_TO_INT(hosts->data);
4585 hosts = g_slist_remove(hosts, hosts->data);
4586 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4587 g_free(hosts->data);
4588 hosts = g_slist_remove(hosts, hosts->data);
4589 while (hosts) {
4590 hosts = g_slist_remove(hosts, hosts->data);
4591 g_free(hosts->data);
4592 hosts = g_slist_remove(hosts, hosts->data);
4595 /* create socket for incoming connections */
4596 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4597 sipe_udp_host_resolved_listen_cb, sip);
4598 if (sip->listen_data == NULL) {
4599 purple_connection_error(sip->gc, _("Could not create listen socket"));
4600 return;
4604 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4605 gpointer data)
4607 PurpleConnection *gc = data;
4608 struct sipe_account_data *sip;
4610 /* If the connection is already disconnected, we don't need to do anything else */
4611 if (!PURPLE_CONNECTION_IS_VALID(gc))
4612 return;
4614 sip = gc->proto_data;
4615 sip->fd = -1;
4616 sip->gsc = NULL;
4618 switch(error) {
4619 case PURPLE_SSL_CONNECT_FAILED:
4620 purple_connection_error(gc, _("Connection Failed"));
4621 break;
4622 case PURPLE_SSL_HANDSHAKE_FAILED:
4623 purple_connection_error(gc, _("SSL Handshake Failed"));
4624 break;
4625 case PURPLE_SSL_CERTIFICATE_INVALID:
4626 purple_connection_error(gc, _("SSL Certificate Invalid"));
4627 break;
4631 static void
4632 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4634 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4635 PurpleProxyConnectData *connect_data;
4637 sip->listen_data = NULL;
4639 sip->listenfd = listenfd;
4640 if (sip->listenfd == -1) {
4641 purple_connection_error(sip->gc, _("Could not create listen socket"));
4642 return;
4645 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4646 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4647 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4648 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4649 sipe_newconn_cb, sip->gc);
4650 purple_debug_info("sipe", "connecting to %s port %d\n",
4651 sip->realhostname, sip->realport);
4652 /* open tcp connection to the server */
4653 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4654 sip->realport, login_cb, sip->gc);
4656 if (connect_data == NULL) {
4657 purple_connection_error(sip->gc, _("Couldn't create socket"));
4662 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4664 PurpleAccount *account = sip->account;
4665 PurpleConnection *gc = sip->gc;
4667 if (purple_account_get_bool(account, "useport", FALSE)) {
4668 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4669 port = purple_account_get_int(account, "port", 0);
4670 } else {
4671 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4674 sip->realhostname = hostname;
4675 sip->realport = port;
4677 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4678 hostname, port);
4680 /* TODO: is there a good default grow size? */
4681 if (sip->transport != SIPE_TRANSPORT_UDP)
4682 sip->txbuf = purple_circ_buffer_new(0);
4684 if (sip->transport == SIPE_TRANSPORT_TLS) {
4685 /* SSL case */
4686 if (!purple_ssl_is_supported()) {
4687 gc->wants_to_die = TRUE;
4688 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4689 return;
4692 purple_debug_info("sipe", "using SSL\n");
4694 sip->gsc = purple_ssl_connect(account, hostname, port,
4695 login_cb_ssl, sipe_ssl_connect_failure, gc);
4696 if (sip->gsc == NULL) {
4697 purple_connection_error(gc, _("Could not create SSL context"));
4698 return;
4700 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4701 /* UDP case */
4702 purple_debug_info("sipe", "using UDP\n");
4704 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4705 if (sip->query_data == NULL) {
4706 purple_connection_error(gc, _("Could not resolve hostname"));
4708 } else {
4709 /* TCP case */
4710 purple_debug_info("sipe", "using TCP\n");
4711 /* create socket for incoming connections */
4712 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4713 sipe_tcp_connect_listen_cb, sip);
4714 if (sip->listen_data == NULL) {
4715 purple_connection_error(gc, _("Could not create listen socket"));
4716 return;
4721 /* Service list for autodection */
4722 static const struct sipe_service_data service_autodetect[] = {
4723 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4724 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4725 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4726 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4727 { NULL, NULL, 0 }
4730 /* Service list for SSL/TLS */
4731 static const struct sipe_service_data service_tls[] = {
4732 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4733 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4734 { NULL, NULL, 0 }
4737 /* Service list for TCP */
4738 static const struct sipe_service_data service_tcp[] = {
4739 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4740 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4741 { NULL, NULL, 0 }
4744 /* Service list for UDP */
4745 static const struct sipe_service_data service_udp[] = {
4746 { "sip", "udp", SIPE_TRANSPORT_UDP },
4747 { NULL, NULL, 0 }
4750 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4751 static void resolve_next_service(struct sipe_account_data *sip,
4752 const struct sipe_service_data *start)
4754 if (start) {
4755 sip->service_data = start;
4756 } else {
4757 sip->service_data++;
4758 if (sip->service_data->service == NULL) {
4759 gchar *hostname;
4760 /* Try connecting to the SIP hostname directly */
4761 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4762 if (sip->auto_transport) {
4763 // If SSL is supported, default to using it; OCS servers aren't configured
4764 // by default to accept TCP
4765 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4766 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4767 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4770 hostname = g_strdup(sip->sipdomain);
4771 create_connection(sip, hostname, 0);
4772 return;
4776 /* Try to resolve next service */
4777 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4778 sip->service_data->transport,
4779 sip->sipdomain,
4780 srvresolved, sip);
4783 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4785 struct sipe_account_data *sip = data;
4787 sip->srv_query_data = NULL;
4789 /* find the host to connect to */
4790 if (results) {
4791 gchar *hostname = g_strdup(resp->hostname);
4792 int port = resp->port;
4793 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4794 hostname, port);
4795 g_free(resp);
4797 sip->transport = sip->service_data->type;
4799 create_connection(sip, hostname, port);
4800 } else {
4801 resolve_next_service(sip, NULL);
4805 static void sipe_login(PurpleAccount *account)
4807 PurpleConnection *gc;
4808 struct sipe_account_data *sip;
4809 gchar **signinname_login, **userserver, **domain_user;
4810 const char *transport;
4812 const char *username = purple_account_get_username(account);
4813 gc = purple_account_get_connection(account);
4815 if (strpbrk(username, "\t\v\r\n") != NULL) {
4816 gc->wants_to_die = TRUE;
4817 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
4818 return;
4821 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4822 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4823 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4824 sip->gc = gc;
4825 sip->account = account;
4826 sip->reregister_set = FALSE;
4827 sip->reauthenticate_set = FALSE;
4828 sip->subscribed = FALSE;
4829 sip->subscribed_buddies = FALSE;
4831 signinname_login = g_strsplit(username, ",", 2);
4833 userserver = g_strsplit(signinname_login[0], "@", 2);
4834 purple_connection_set_display_name(gc, userserver[0]);
4835 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4836 sip->sipdomain = g_strdup(userserver[1]);
4838 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
4839 gc->wants_to_die = TRUE;
4840 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4841 return;
4844 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4845 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4846 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4848 sip->password = g_strdup(purple_connection_get_password(gc));
4850 g_strfreev(userserver);
4851 g_strfreev(domain_user);
4852 g_strfreev(signinname_login);
4854 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4856 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4858 /* TODO: Set the status correctly. */
4859 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4861 transport = purple_account_get_string(account, "transport", "auto");
4862 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4863 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4864 SIPE_TRANSPORT_UDP;
4866 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4867 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4868 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4869 } else if (strcmp(transport, "auto") == 0) {
4870 sip->auto_transport = TRUE;
4871 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4872 } else if (strcmp(transport, "tls") == 0) {
4873 resolve_next_service(sip, service_tls);
4874 } else if (strcmp(transport, "tcp") == 0) {
4875 resolve_next_service(sip, service_tcp);
4876 } else {
4877 resolve_next_service(sip, service_udp);
4881 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4883 connection_free_all(sip);
4885 g_free(sip->epid);
4886 sip->epid = NULL;
4888 if (sip->query_data != NULL)
4889 purple_dnsquery_destroy(sip->query_data);
4890 sip->query_data = NULL;
4892 if (sip->srv_query_data != NULL)
4893 purple_srv_cancel(sip->srv_query_data);
4894 sip->srv_query_data = NULL;
4896 if (sip->listen_data != NULL)
4897 purple_network_listen_cancel(sip->listen_data);
4898 sip->listen_data = NULL;
4900 if (sip->gsc != NULL)
4901 purple_ssl_close(sip->gsc);
4902 sip->gsc = NULL;
4904 sipe_auth_free(&sip->registrar);
4905 sipe_auth_free(&sip->proxy);
4907 if (sip->txbuf)
4908 purple_circ_buffer_destroy(sip->txbuf);
4909 sip->txbuf = NULL;
4911 g_free(sip->realhostname);
4912 sip->realhostname = NULL;
4914 if (sip->listenpa)
4915 purple_input_remove(sip->listenpa);
4916 sip->listenpa = 0;
4917 if (sip->tx_handler)
4918 purple_input_remove(sip->tx_handler);
4919 sip->tx_handler = 0;
4920 if (sip->resendtimeout)
4921 purple_timeout_remove(sip->resendtimeout);
4922 sip->resendtimeout = 0;
4923 if (sip->timeouts) {
4924 GSList *entry = sip->timeouts;
4925 while (entry) {
4926 struct scheduled_action *sched_action = entry->data;
4927 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4928 purple_timeout_remove(sched_action->timeout_handler);
4929 g_free(sched_action->payload);
4930 g_free(sched_action->name);
4931 g_free(sched_action);
4932 entry = entry->next;
4935 g_slist_free(sip->timeouts);
4937 if (sip->allow_events) {
4938 GSList *entry = sip->allow_events;
4939 while (entry) {
4940 g_free(entry->data);
4941 entry = entry->next;
4944 g_slist_free(sip->allow_events);
4946 if (sip->contact)
4947 g_free(sip->contact);
4948 sip->contact = NULL;
4949 if (sip->regcallid)
4950 g_free(sip->regcallid);
4951 sip->regcallid = NULL;
4953 sip->fd = -1;
4954 sip->processing_input = FALSE;
4958 * A callback for g_hash_table_foreach_remove
4960 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4962 sipe_free_buddy(buddy);
4965 static void sipe_close(PurpleConnection *gc)
4967 struct sipe_account_data *sip = gc->proto_data;
4969 if (sip) {
4970 /* leave all conversations */
4971 im_session_close_all(sip);
4973 /* unregister */
4974 do_register_exp(sip, 0);
4976 sipe_connection_cleanup(sip);
4977 g_free(sip->sipdomain);
4978 g_free(sip->username);
4979 g_free(sip->password);
4980 g_free(sip->authdomain);
4981 g_free(sip->authuser);
4982 g_free(sip->status);
4984 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4985 g_hash_table_destroy(sip->buddies);
4987 g_free(gc->proto_data);
4988 gc->proto_data = NULL;
4991 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4993 PurpleAccount *acct = purple_connection_get_account(gc);
4994 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4995 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4996 if (conv == NULL)
4997 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4998 purple_conversation_present(conv);
4999 g_free(id);
5002 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5005 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5006 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
5009 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5011 PurpleNotifySearchResults *results;
5012 PurpleNotifySearchColumn *column;
5013 xmlnode *searchResults;
5014 xmlnode *mrow;
5015 int match_count = 0;
5016 gboolean more = FALSE;
5017 gchar *secondary;
5019 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5021 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5022 if (!searchResults) {
5023 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5024 return FALSE;
5027 results = purple_notify_searchresults_new();
5029 if (results == NULL) {
5030 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5031 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5033 xmlnode_free(searchResults);
5034 return FALSE;
5037 column = purple_notify_searchresults_column_new(_("User Name"));
5038 purple_notify_searchresults_column_add(results, column);
5040 column = purple_notify_searchresults_column_new(_("Name"));
5041 purple_notify_searchresults_column_add(results, column);
5043 column = purple_notify_searchresults_column_new(_("Company"));
5044 purple_notify_searchresults_column_add(results, column);
5046 column = purple_notify_searchresults_column_new(_("Country"));
5047 purple_notify_searchresults_column_add(results, column);
5049 column = purple_notify_searchresults_column_new(_("Email"));
5050 purple_notify_searchresults_column_add(results, column);
5052 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5053 GList *row = NULL;
5055 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5056 row = g_list_append(row, g_strdup(uri_parts[1]));
5057 g_strfreev(uri_parts);
5059 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5060 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5061 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5062 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5064 purple_notify_searchresults_row_add(results, row);
5065 match_count++;
5068 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5069 char *data = xmlnode_get_data_unescaped(mrow);
5070 more = (g_strcasecmp(data, "true") == 0);
5071 g_free(data);
5074 secondary = g_strdup_printf(
5075 dngettext(GETTEXT_PACKAGE,
5076 "Found %d contact%s:",
5077 "Found %d contacts%s:", match_count),
5078 match_count, more ? _(" (more matched your query)") : "");
5080 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5081 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5082 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5084 g_free(secondary);
5085 xmlnode_free(searchResults);
5086 return TRUE;
5089 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5091 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5092 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5093 unsigned i = 0;
5095 do {
5096 PurpleRequestField *field = entries->data;
5097 const char *id = purple_request_field_get_id(field);
5098 const char *value = purple_request_field_string_get_value(field);
5100 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5102 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5103 } while ((entries = g_list_next(entries)) != NULL);
5104 attrs[i] = NULL;
5106 if (i > 0) {
5107 gchar *query = g_strjoinv(NULL, attrs);
5108 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5109 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5110 send_soap_request_with_cb(gc->proto_data, body,
5111 (TransCallback) process_search_contact_response, NULL);
5112 g_free(body);
5113 g_free(query);
5116 g_strfreev(attrs);
5119 static void sipe_show_find_contact(PurplePluginAction *action)
5121 PurpleConnection *gc = (PurpleConnection *) action->context;
5122 PurpleRequestFields *fields;
5123 PurpleRequestFieldGroup *group;
5124 PurpleRequestField *field;
5126 fields = purple_request_fields_new();
5127 group = purple_request_field_group_new(NULL);
5128 purple_request_fields_add_group(fields, group);
5130 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5131 purple_request_field_group_add_field(group, field);
5132 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5133 purple_request_field_group_add_field(group, field);
5134 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5135 purple_request_field_group_add_field(group, field);
5136 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5137 purple_request_field_group_add_field(group, field);
5139 purple_request_fields(gc,
5140 _("Search"),
5141 _("Search for a Contact"),
5142 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5143 fields,
5144 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5145 _("_Cancel"), NULL,
5146 purple_connection_get_account(gc), NULL, NULL, gc);
5149 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5151 GList *menu = NULL;
5152 PurplePluginAction *act;
5154 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5155 menu = g_list_prepend(menu, act);
5157 menu = g_list_reverse(menu);
5159 return menu;
5162 static void dummy_permit_deny(PurpleConnection *gc)
5166 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5168 return TRUE;
5172 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5174 return TRUE;
5178 static char *sipe_status_text(PurpleBuddy *buddy)
5180 struct sipe_account_data *sip;
5181 struct sipe_buddy *sbuddy;
5182 char *text = NULL;
5184 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5185 if (sip) //happens on pidgin exit
5187 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5188 if (sbuddy && sbuddy->annotation)
5190 text = g_strdup(sbuddy->annotation);
5194 return text;
5197 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5199 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5200 const PurpleStatus *status = purple_presence_get_active_status(presence);
5201 struct sipe_account_data *sip;
5202 struct sipe_buddy *sbuddy;
5203 char *annotation = NULL;
5205 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5206 if (sip) //happens on pidgin exit
5208 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5209 if (sbuddy)
5211 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5215 //Layout
5216 if (purple_presence_is_online(presence))
5218 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5221 if (annotation)
5223 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5224 g_free(annotation);
5229 static GHashTable *
5230 sipe_get_account_text_table(PurpleAccount *account)
5232 GHashTable *table;
5233 table = g_hash_table_new(g_str_hash, g_str_equal);
5234 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5235 return table;
5238 static PurpleBuddy *
5239 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5241 PurpleBuddy *clone;
5242 const gchar *server_alias, *email;
5243 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5245 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5247 purple_blist_add_buddy(clone, NULL, group, NULL);
5249 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5250 if (server_alias) {
5251 purple_blist_server_alias_buddy(clone, server_alias);
5254 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5255 if (email) {
5256 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5259 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5260 //for UI to update;
5261 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5262 return clone;
5265 static void
5266 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5268 PurpleBuddy *buddy, *b;
5269 PurpleConnection *gc;
5270 PurpleGroup * group = purple_find_group(group_name);
5272 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5274 buddy = (PurpleBuddy *)node;
5276 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5277 gc = purple_account_get_connection(buddy->account);
5279 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5280 if (!b){
5281 b = purple_blist_add_buddy_clone(group, buddy);
5284 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5287 static void
5288 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5290 const gchar *email;
5291 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5293 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5294 if (email)
5296 char *mailto = g_strdup_printf("mailto:%s", email);
5297 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5298 #ifndef _WIN32
5300 pid_t pid;
5301 char *const parmList[] = {mailto, NULL};
5302 if ((pid = fork()) == -1)
5304 purple_debug_info("sipe", "fork() error\n");
5306 else if (pid == 0)
5308 execvp("xdg-email", parmList);
5309 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5312 #else
5314 BOOL ret;
5315 _flushall();
5316 errno = 0;
5317 //@TODO resolve env variable %WINDIR% first
5318 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5319 if (errno)
5321 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5324 #endif
5326 g_free(mailto);
5328 else
5330 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5335 * A menu which appear when right-clicking on buddy in contact list.
5337 static GList *
5338 sipe_buddy_menu(PurpleBuddy *buddy)
5340 PurpleBlistNode *g_node;
5341 PurpleGroup *group, *gr_parent;
5342 PurpleMenuAction *act;
5343 GList *menu = NULL;
5344 GList *menu_groups = NULL;
5346 act = purple_menu_action_new(_("Send Email..."),
5347 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5348 NULL, NULL);
5349 menu = g_list_prepend(menu, act);
5351 gr_parent = purple_buddy_get_group(buddy);
5352 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5353 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5354 continue;
5356 group = (PurpleGroup *)g_node;
5357 if (group == gr_parent)
5358 continue;
5360 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5361 continue;
5363 act = purple_menu_action_new(purple_group_get_name(group),
5364 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5365 group->name, NULL);
5366 menu_groups = g_list_prepend(menu_groups, act);
5368 menu_groups = g_list_reverse(menu_groups);
5370 act = purple_menu_action_new(_("Copy to"),
5371 NULL,
5372 NULL, menu_groups);
5373 menu = g_list_prepend(menu, act);
5374 menu = g_list_reverse(menu);
5376 return menu;
5379 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5380 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5381 return sipe_buddy_menu((PurpleBuddy *) node);
5382 } else {
5383 return NULL;
5387 static gboolean
5388 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5390 gboolean ret = TRUE;
5391 char *username = (char *)trans->payload;
5393 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5394 PurpleBuddy *pbuddy;
5395 struct sipe_buddy *sbuddy;
5396 const char *alias;
5397 char *server_alias = NULL;
5398 char *email = NULL;
5399 const char *device_name = NULL;
5401 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5403 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5404 alias = purple_buddy_get_local_alias(pbuddy);
5406 if (sip)
5408 //will query buddy UA's capabilities and send answer to log
5409 sipe_options_request(sip, username);
5411 sbuddy = g_hash_table_lookup(sip->buddies, username);
5412 if (sbuddy)
5414 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5418 if (msg->response != 200) {
5419 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5420 } else {
5421 xmlnode *searchResults;
5422 xmlnode *mrow;
5424 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5425 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5426 if (!searchResults) {
5427 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5428 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5429 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5430 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5431 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5432 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5433 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5434 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5435 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5436 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5437 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5438 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5439 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5440 if (!email || strcmp("", email)) {
5441 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5442 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5446 xmlnode_free(searchResults);
5449 purple_notify_user_info_add_section_break(info);
5451 if (!server_alias || !strcmp("", server_alias)) {
5452 g_free(server_alias);
5453 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5454 if (server_alias) {
5455 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5459 // same as server alias, do not present
5460 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5461 if (alias)
5463 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5466 if (!email || !strcmp("", email)) {
5467 g_free(email);
5468 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5469 if (email) {
5470 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5474 if (device_name)
5476 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5479 /* show a buddy's user info in a nice dialog box */
5480 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5481 username, /* buddy's username */
5482 info, /* body */
5483 NULL, /* callback called when dialog closed */
5484 NULL); /* userdata for callback */
5486 return ret;
5490 * AD search first, LDAP based
5492 static void sipe_get_info(PurpleConnection *gc, const char *username)
5494 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5495 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5497 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5498 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5499 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5500 g_free(body);
5501 g_free(row);
5504 static PurplePlugin *my_protocol = NULL;
5506 static PurplePluginProtocolInfo prpl_info =
5509 NULL, /* user_splits */
5510 NULL, /* protocol_options */
5511 NO_BUDDY_ICONS, /* icon_spec */
5512 sipe_list_icon, /* list_icon */
5513 NULL, /* list_emblems */
5514 sipe_status_text, /* status_text */
5515 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5516 sipe_status_types, /* away_states */
5517 sipe_blist_node_menu, /* blist_node_menu */
5518 NULL, /* chat_info */
5519 NULL, /* chat_info_defaults */
5520 sipe_login, /* login */
5521 sipe_close, /* close */
5522 sipe_im_send, /* send_im */
5523 NULL, /* set_info */ // TODO maybe
5524 sipe_send_typing, /* send_typing */
5525 sipe_get_info, /* get_info */
5526 sipe_set_status, /* set_status */
5527 NULL, /* set_idle */
5528 NULL, /* change_passwd */
5529 sipe_add_buddy, /* add_buddy */
5530 NULL, /* add_buddies */
5531 sipe_remove_buddy, /* remove_buddy */
5532 NULL, /* remove_buddies */
5533 sipe_add_permit, /* add_permit */
5534 sipe_add_deny, /* add_deny */
5535 sipe_add_deny, /* rem_permit */
5536 sipe_add_permit, /* rem_deny */
5537 dummy_permit_deny, /* set_permit_deny */
5538 NULL, /* join_chat */
5539 NULL, /* reject_chat */
5540 NULL, /* get_chat_name */
5541 NULL, /* chat_invite */
5542 NULL, /* chat_leave */
5543 NULL, /* chat_whisper */
5544 NULL, /* chat_send */
5545 sipe_keep_alive, /* keepalive */
5546 NULL, /* register_user */
5547 NULL, /* get_cb_info */ // deprecated
5548 NULL, /* get_cb_away */ // deprecated
5549 sipe_alias_buddy, /* alias_buddy */
5550 sipe_group_buddy, /* group_buddy */
5551 sipe_rename_group, /* rename_group */
5552 NULL, /* buddy_free */
5553 sipe_convo_closed, /* convo_closed */
5554 purple_normalize_nocase, /* normalize */
5555 NULL, /* set_buddy_icon */
5556 sipe_remove_group, /* remove_group */
5557 NULL, /* get_cb_real_name */ // TODO?
5558 NULL, /* set_chat_topic */
5559 NULL, /* find_blist_chat */
5560 NULL, /* roomlist_get_list */
5561 NULL, /* roomlist_cancel */
5562 NULL, /* roomlist_expand_category */
5563 NULL, /* can_receive_file */
5564 NULL, /* send_file */
5565 NULL, /* new_xfer */
5566 NULL, /* offline_message */
5567 NULL, /* whiteboard_prpl_ops */
5568 sipe_send_raw, /* send_raw */
5569 NULL, /* roomlist_room_serialize */
5570 NULL, /* unregister_user */
5571 NULL, /* send_attention */
5572 NULL, /* get_attention_types */
5574 sizeof(PurplePluginProtocolInfo), /* struct_size */
5575 sipe_get_account_text_table, /* get_account_text_table */
5579 static PurplePluginInfo info = {
5580 PURPLE_PLUGIN_MAGIC,
5581 PURPLE_MAJOR_VERSION,
5582 PURPLE_MINOR_VERSION,
5583 PURPLE_PLUGIN_PROTOCOL, /**< type */
5584 NULL, /**< ui_requirement */
5585 0, /**< flags */
5586 NULL, /**< dependencies */
5587 PURPLE_PRIORITY_DEFAULT, /**< priority */
5588 "prpl-sipe", /**< id */
5589 "Microsoft LCS/OCS", /**< name */
5590 VERSION, /**< version */
5591 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5592 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5593 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5594 "Gabriel Burt <gburt@novell.com>", /**< author */
5595 PURPLE_WEBSITE, /**< homepage */
5596 sipe_plugin_load, /**< load */
5597 sipe_plugin_unload, /**< unload */
5598 sipe_plugin_destroy, /**< destroy */
5599 NULL, /**< ui_info */
5600 &prpl_info, /**< extra_info */
5601 NULL,
5602 sipe_actions,
5603 NULL,
5604 NULL,
5605 NULL,
5606 NULL
5609 static void sipe_plugin_destroy(PurplePlugin *plugin)
5611 GList *entry;
5613 entry = prpl_info.protocol_options;
5614 while (entry) {
5615 purple_account_option_destroy(entry->data);
5616 entry = g_list_delete_link(entry, entry);
5618 prpl_info.protocol_options = NULL;
5620 entry = prpl_info.user_splits;
5621 while (entry) {
5622 purple_account_user_split_destroy(entry->data);
5623 entry = g_list_delete_link(entry, entry);
5625 prpl_info.user_splits = NULL;
5628 static void init_plugin(PurplePlugin *plugin)
5630 PurpleAccountUserSplit *split;
5631 PurpleAccountOption *option;
5633 #ifdef ENABLE_NLS
5634 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5635 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5636 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5637 #endif
5639 purple_plugin_register(plugin);
5641 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5642 purple_account_user_split_set_reverse(split, FALSE);
5643 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5645 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5646 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5647 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5648 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5650 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5651 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5652 // Translators: noun (networking port)
5653 option = purple_account_option_int_new(_("Port"), "port", 5061);
5654 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5656 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5657 purple_account_option_add_list_item(option, _("Auto"), "auto");
5658 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5659 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5660 purple_account_option_add_list_item(option, _("UDP"), "udp");
5661 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5663 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5664 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5666 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5667 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5669 // TODO commented out so won't show in the preferences until we fix krb message signing
5670 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5671 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5673 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5674 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5675 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5678 my_protocol = plugin;
5681 /* I had to redefined the function for it load, but works */
5682 gboolean purple_init_plugin(PurplePlugin *plugin){
5683 plugin->info = &(info);
5684 init_plugin((plugin));
5685 sipe_plugin_load((plugin));
5686 return purple_plugin_register(plugin);
5690 Local Variables:
5691 mode: c
5692 c-file-style: "bsd"
5693 indent-tabs-mode: t
5694 tab-width: 8
5695 End: