Merge branch 'mob' of git+ssh://mob@repo.or.cz/srv/git/siplcs into mob
[siplcs.git] / src / sipe.c
blob44f230d2f375a721979adab57509f12b590d4a35
1 /**
2 * @file sipe.c
4 * pidgin-sipe
5 *
6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
11 * ***
12 * Thanks to Google's Summer of Code Program and the helpful mentors
13 * ***
15 * Session-based SIP MESSAGE documentation:
16 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #ifndef _WIN32
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <netinet/in.h>
38 #include <net/if.h>
39 #ifdef ENABLE_NLS
40 # include <libintl.h>
41 # define _(String) ((const char *) gettext (String))
42 #else
43 # define _(String) ((const char *) (String))
44 #endif /* ENABLE_NLS */
45 #else
46 #ifdef _DLL
47 #define _WS2TCPIP_H_
48 #define _WINSOCK2API_
49 #define _LIBC_INTERNAL_
50 #endif /* _DLL */
52 #include "internal.h"
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <string.h>
59 #include <glib.h>
62 #include "accountopt.h"
63 #include "blist.h"
64 #include "conversation.h"
65 #include "dnsquery.h"
66 #include "debug.h"
67 #include "notify.h"
68 #include "privacy.h"
69 #include "prpl.h"
70 #include "plugin.h"
71 #include "util.h"
72 #include "version.h"
73 #include "network.h"
74 #include "xmlnode.h"
75 #include "mime.h"
77 #include "sipe.h"
78 #include "sip-ntlm.h"
79 #ifdef USE_KERBEROS
80 #include "sipkrb5.h"
81 #endif /*USE_KERBEROS*/
83 #include "sipmsg.h"
84 #include "sipe-sign.h"
85 #include "dnssrv.h"
86 #include "request.h"
88 /* Keep in sync with sipe_transport_type! */
89 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
90 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
92 /* Status identifiers (see also: sipe_status_types()) */
93 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
94 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
95 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
96 /* PURPLE_STATUS_UNAVAILABLE: */
97 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
98 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
99 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
100 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
101 /* PURPLE_STATUS_AWAY: */
102 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
103 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
104 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
105 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
106 /* ??? PURPLE_STATUS_MOBILE */
107 /* ??? PURPLE_STATUS_TUNE */
109 static char *gentag()
111 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
114 static gchar *get_epid(struct sipe_account_data *sip)
116 if (!sip->epid) {
117 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
119 return g_strdup(sip->epid);
122 static char *genbranch()
124 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
125 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
126 rand() & 0xFFFF, rand() & 0xFFFF);
129 static char *gencallid()
131 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
132 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
133 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
134 rand() & 0xFFFF, rand() & 0xFFFF);
137 static gchar *find_tag(const gchar *hdr)
139 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
140 if (!tag) {
141 // In case it's at the end and there's no trailing ;
142 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
144 return tag;
148 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
150 return "sipe";
153 static void sipe_plugin_destroy(PurplePlugin *plugin);
155 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
157 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
158 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
159 gpointer data);
161 static void sipe_close(PurpleConnection *gc);
163 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name);
164 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip);
165 static void send_presence_status(struct sipe_account_data *sip);
167 static void sendout_pkt(PurpleConnection *gc, const char *buf);
169 static void sipe_keep_alive(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){
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;
1556 if (sip->msrtc_event_categories) {
1558 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1560 require = ", categoryList";
1561 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1562 content_type = "application/msrtc-adrl-categorylist+xml";
1563 content = g_strdup_printf(
1564 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1565 "<action name=\"subscribe\" id=\"63792024\">\n"
1566 "<adhocList>\n%s</adhocList>\n"
1567 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1568 "<category name=\"note\"/>\n"
1569 "<category name=\"state\"/>\n"
1570 "</categoryList>\n"
1571 "</action>\n"
1572 "</batchSub>", sip->username, resources_uri);
1573 } else {
1574 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1575 autoextend = "Supported: com.microsoft.autoextend\r\n";
1576 content_type = "application/adrl+xml";
1577 content = g_strdup_printf(
1578 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1579 "<create xmlns=\"\">\n%s</create>\n"
1580 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1582 g_free(resources_uri);
1584 request = g_strdup_printf(
1585 "Require: adhoclist%s\r\n"
1586 "Supported: eventlist\r\n"
1587 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1588 "Supported: ms-piggyback-first-notify\r\n"
1589 "%sSupported: ms-benotify\r\n"
1590 "Proxy-Require: ms-benotify\r\n"
1591 "Event: presence\r\n"
1592 "Content-Type: %s\r\n"
1593 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1594 g_free(contact);
1596 /* subscribe to buddy presence */
1597 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1598 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1600 g_free(content);
1601 g_free(to);
1602 g_free(request);
1606 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1607 * The user sends a single SUBSCRIBE request to the subscribed contact.
1608 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1612 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1614 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1615 gchar *tmp = get_contact(sip);
1616 gchar *request;
1617 gchar *content;
1618 gchar *autoextend = "";
1620 if (!sip->msrtc_event_categories)
1621 autoextend = "Supported: com.microsoft.autoextend\r\n";
1623 request = g_strdup_printf(
1624 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1625 "Supported: ms-piggyback-first-notify\r\n"
1626 "%sSupported: ms-benotify\r\n"
1627 "Proxy-Require: ms-benotify\r\n"
1628 "Event: presence\r\n"
1629 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1630 "Contact: %s\r\n", autoextend,tmp);
1632 content = g_strdup_printf(
1633 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1634 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1635 "<resource uri=\"%s\"/>\n"
1636 "</adhocList>\n"
1637 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1638 "<category name=\"note\"/>\n"
1639 "<category name=\"state\"/>\n"
1640 "</categoryList>\n"
1641 "</action>\n"
1642 "</batchSub>", sip->username, to
1645 g_free(tmp);
1647 /* subscribe to buddy presence */
1648 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1650 g_free(content);
1651 g_free(to);
1652 g_free(request);
1655 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1657 if (!purple_status_is_active(status))
1658 return;
1660 if (account->gc) {
1661 struct sipe_account_data *sip = account->gc->proto_data;
1663 if (sip) {
1664 g_free(sip->status);
1665 sip->status = g_strdup(purple_status_get_id(status));
1666 send_presence_status(sip);
1671 static void
1672 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1674 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1675 sipe_group_set_user(sip, name);
1678 static void
1679 sipe_group_buddy(PurpleConnection *gc,
1680 const char *who,
1681 const char *old_group_name,
1682 const char *new_group_name)
1684 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1685 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1686 struct sipe_group * old_group = NULL;
1687 struct sipe_group * new_group;
1689 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1690 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1692 if(!buddy) { // buddy not in roaming list
1693 return;
1696 if (old_group_name) {
1697 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1699 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1701 if (old_group) {
1702 buddy->groups = g_slist_remove(buddy->groups, old_group);
1703 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1706 if (!new_group) {
1707 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1708 } else {
1709 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1710 sipe_group_set_user(sip, who);
1714 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1716 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1717 struct sipe_buddy *b;
1719 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1721 // Prepend sip: if needed
1722 if (strncmp("sip:", buddy->name, 4)) {
1723 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1724 purple_blist_rename_buddy(buddy, buf);
1725 g_free(buf);
1728 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1729 b = g_new0(struct sipe_buddy, 1);
1730 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1731 b->name = g_strdup(buddy->name);
1732 g_hash_table_insert(sip->buddies, b->name, b);
1733 sipe_group_buddy(gc, b->name, NULL, group->name);
1734 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1735 } else {
1736 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1740 static void sipe_free_buddy(struct sipe_buddy *buddy)
1742 g_free(buddy->name);
1743 g_free(buddy->annotation);
1744 g_free(buddy->device_name);
1745 g_slist_free(buddy->groups);
1746 g_free(buddy);
1750 * Unassociates buddy from group first.
1751 * Then see if no groups left, removes buddy completely.
1752 * Otherwise updates buddy groups on server.
1754 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1756 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1757 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1758 struct sipe_group *g = NULL;
1760 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1762 if (!b) return;
1764 if (group) {
1765 g = sipe_group_find_by_name(sip, group->name);
1768 if (g) {
1769 b->groups = g_slist_remove(b->groups, g);
1770 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1773 if (g_slist_length(b->groups) < 1) {
1774 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1775 sipe_cancel_scheduled_action(sip, action_name);
1776 g_free(action_name);
1778 g_hash_table_remove(sip->buddies, buddy->name);
1780 if (b->name) {
1781 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1782 send_soap_request(sip, body);
1783 g_free(body);
1786 sipe_free_buddy(b);
1787 } else {
1788 //updates groups on server
1789 sipe_group_set_user(sip, b->name);
1794 static void
1795 sipe_rename_group(PurpleConnection *gc,
1796 const char *old_name,
1797 PurpleGroup *group,
1798 GList *moved_buddies)
1800 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1801 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1802 if (group) {
1803 sipe_group_rename(sip, s_group, group->name);
1804 } else {
1805 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1809 static void
1810 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1812 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1813 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1814 if (s_group) {
1815 gchar *body;
1816 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1817 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1818 send_soap_request(sip, body);
1819 g_free(body);
1821 sip->groups = g_slist_remove(sip->groups, s_group);
1822 g_free(s_group->name);
1823 g_free(s_group);
1824 } else {
1825 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1829 static GList *sipe_status_types(PurpleAccount *acc)
1831 PurpleStatusType *type;
1832 GList *types = NULL;
1834 // Online
1835 type = purple_status_type_new_with_attrs(
1836 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1837 // Translators: noun
1838 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1839 NULL);
1840 types = g_list_append(types, type);
1842 // Busy
1843 type = purple_status_type_new_with_attrs(
1844 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1845 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1846 NULL);
1847 types = g_list_append(types, type);
1849 // Do Not Disturb (not user settable)
1850 type = purple_status_type_new_with_attrs(
1851 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1852 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1853 NULL);
1854 types = g_list_append(types, type);
1856 // Be Right Back
1857 type = purple_status_type_new_with_attrs(
1858 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1859 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1860 NULL);
1861 types = g_list_append(types, type);
1863 // Away
1864 type = purple_status_type_new_with_attrs(
1865 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1866 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1867 NULL);
1868 types = g_list_append(types, type);
1870 //On The Phone
1871 type = purple_status_type_new_with_attrs(
1872 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1873 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1874 NULL);
1875 types = g_list_append(types, type);
1877 //Out To Lunch
1878 type = purple_status_type_new_with_attrs(
1879 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1880 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1881 NULL);
1882 types = g_list_append(types, type);
1884 //Appear Offline
1885 type = purple_status_type_new_full(
1886 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1887 types = g_list_append(types, type);
1889 // Offline
1890 type = purple_status_type_new_full(
1891 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1892 types = g_list_append(types, type);
1894 return types;
1898 * A callback for g_hash_table_foreach
1900 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1902 sipe_subscribe_presence_single(sip, buddy->name);
1906 * Removes entries from purple buddy list
1907 * that does not correspond ones in the roaming contact list.
1909 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1910 GSList *buddies = purple_find_buddies(sip->account, NULL);
1911 GSList *entry = buddies;
1912 struct sipe_buddy *buddy;
1913 PurpleBuddy *b;
1914 PurpleGroup *g;
1916 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1917 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1918 while (entry) {
1919 b = entry->data;
1920 g = purple_buddy_get_group(b);
1921 buddy = g_hash_table_lookup(sip->buddies, b->name);
1922 if(buddy) {
1923 gboolean in_sipe_groups = FALSE;
1924 GSList *entry2 = buddy->groups;
1925 while (entry2) {
1926 struct sipe_group *group = entry2->data;
1927 if (!strcmp(group->name, g->name)) {
1928 in_sipe_groups = TRUE;
1929 break;
1931 entry2 = entry2->next;
1933 if(!in_sipe_groups) {
1934 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1935 purple_blist_remove_buddy(b);
1937 } else {
1938 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1939 purple_blist_remove_buddy(b);
1941 entry = entry->next;
1945 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1947 int len = msg->bodylen;
1949 gchar *tmp = sipmsg_find_header(msg, "Event");
1950 xmlnode *item;
1951 xmlnode *isc;
1952 const gchar *contacts_delta;
1953 xmlnode *group_node;
1954 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1955 return FALSE;
1958 /* Convert the contact from XML to Purple Buddies */
1959 isc = xmlnode_from_str(msg->body, len);
1960 if (!isc) {
1961 return FALSE;
1964 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1965 if (contacts_delta) {
1966 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1969 /* Parse groups */
1970 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1971 struct sipe_group * group = g_new0(struct sipe_group, 1);
1972 const char *name = xmlnode_get_attrib(group_node, "name");
1974 if (!strncmp(name, "~", 1)) {
1975 // TODO translate
1976 name = "Other Contacts";
1978 group->name = g_strdup(name);
1979 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1981 sipe_group_add(sip, group);
1984 // Make sure we have at least one group
1985 if (g_slist_length(sip->groups) == 0) {
1986 struct sipe_group * group = g_new0(struct sipe_group, 1);
1987 PurpleGroup *purple_group;
1988 // TODO translate
1989 group->name = g_strdup("Other Contacts");
1990 group->id = 1;
1991 purple_group = purple_group_new(group->name);
1992 purple_blist_add_group(purple_group, NULL);
1993 sip->groups = g_slist_append(sip->groups, group);
1996 /* Parse contacts */
1997 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1998 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1999 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
2000 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
2001 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
2002 gchar **item_groups;
2003 struct sipe_group *group = NULL;
2004 struct sipe_buddy *buddy = NULL;
2005 int i = 0;
2007 // assign to group Other Contacts if nothing else received
2008 if(!groups || !strcmp("", groups) ) {
2009 group = sipe_group_find_by_name(sip, "Other Contacts");
2010 groups = group ? g_strdup_printf("%d", group->id) : "1";
2013 item_groups = g_strsplit(groups, " ", 0);
2015 while (item_groups[i]) {
2016 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2018 // If couldn't find the right group for this contact, just put them in the first group we have
2019 if (group == NULL && g_slist_length(sip->groups) > 0) {
2020 group = sip->groups->data;
2023 if (group != NULL) {
2024 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2025 if (!b){
2026 b = purple_buddy_new(sip->account, buddy_name, uri);
2027 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2030 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2031 if (name != NULL && strlen(name) != 0) {
2032 purple_blist_alias_buddy(b, name);
2036 if (!buddy) {
2037 buddy = g_new0(struct sipe_buddy, 1);
2038 buddy->name = g_strdup(b->name);
2039 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2042 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2044 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2045 } else {
2046 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2047 name);
2050 i++;
2051 } // while, contact groups
2052 g_strfreev(item_groups);
2053 g_free(groups);
2054 g_free(name);
2055 g_free(buddy_name);
2056 g_free(uri);
2058 } // for, contacts
2060 xmlnode_free(isc);
2062 sipe_cleanup_local_blist(sip);
2064 //subscribe to buddies
2065 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2066 if(sip->batched_support){
2067 sipe_subscribe_presence_batched(sip);
2069 else{
2070 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2072 sip->subscribed_buddies = TRUE;
2075 return 0;
2079 * Subscribe roaming contacts
2081 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2083 gchar *to = g_strdup_printf("sip:%s", sip->username);
2084 gchar *tmp = get_contact(sip);
2085 gchar *hdr = g_strdup_printf(
2086 "Event: vnd-microsoft-roaming-contacts\r\n"
2087 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2088 "Supported: com.microsoft.autoextend\r\n"
2089 "Supported: ms-benotify\r\n"
2090 "Proxy-Require: ms-benotify\r\n"
2091 "Supported: ms-piggyback-first-notify\r\n"
2092 "Contact: %s\r\n", tmp);
2093 g_free(tmp);
2095 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2096 g_free(to);
2097 g_free(hdr);
2100 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2102 gchar *to = g_strdup_printf("sip:%s", sip->username);
2103 gchar *tmp = get_contact(sip);
2104 gchar *hdr = g_strdup_printf(
2105 "Event: presence.wpending\r\n"
2106 "Accept: text/xml+msrtc.wpending\r\n"
2107 "Supported: com.microsoft.autoextend\r\n"
2108 "Supported: ms-benotify\r\n"
2109 "Proxy-Require: ms-benotify\r\n"
2110 "Supported: ms-piggyback-first-notify\r\n"
2111 "Contact: %s\r\n", tmp);
2112 g_free(tmp);
2114 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2115 g_free(to);
2116 g_free(hdr);
2120 * Fires on deregistration event initiated by server.
2121 * [MS-SIPREGE] SIP extension.
2124 // 2007 Example
2126 // Content-Type: text/registration-event
2127 // subscription-state: terminated;expires=0
2128 // ms-diagnostics-public: 4141;reason="User disabled"
2130 // deregistered;event=rejected
2132 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2134 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2135 gchar *event = NULL;
2136 gchar *reason = NULL;
2137 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2139 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2140 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2142 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2143 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2144 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2145 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2146 } else {
2147 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2148 return;
2151 if (warning != NULL) {
2152 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2153 } else { // for LCS2005
2154 int error_id = 0;
2155 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2156 error_id = 4140; // [MS-SIPREGE]
2157 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2158 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2159 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2160 error_id = 4141;
2161 reason = g_strdup(_("User disabled")); // [MS-OCER]
2162 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2163 error_id = 4142;
2164 reason = g_strdup(_("User moved")); // [MS-OCER]
2167 g_free(event);
2168 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2169 g_free(reason);
2171 sip->gc->wants_to_die = TRUE;
2172 purple_connection_error(sip->gc, warning);
2173 g_free(warning);
2177 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2179 const gchar *contacts_delta;
2180 xmlnode *xml;
2182 xml = xmlnode_from_str(msg->body, msg->bodylen);
2183 if (!xml)
2185 return;
2188 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2189 if (contacts_delta)
2191 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2194 xmlnode_free(xml);
2200 * When we receive some self (BE) NOTIFY with a new subscriber
2201 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2205 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2207 gchar *contact;
2208 gchar *to;
2209 xmlnode *xml;
2210 xmlnode *node;
2211 char *display_name = NULL;
2212 PurpleBuddy *pbuddy;
2213 const char *alias;
2214 char *uri_alias;
2215 char *uri_user;
2217 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2219 xml = xmlnode_from_str(msg->body, msg->bodylen);
2220 if (!xml) return;
2222 contact = get_contact(sip);
2223 to = g_strdup_printf("sip:%s", sip->username);
2225 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2226 const char *user;
2227 const char *acknowledged;
2228 gchar *hdr;
2229 gchar *body;
2231 user = xmlnode_get_attrib(node, "user");
2232 if (!user) continue;
2233 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2234 uri_user = g_strdup_printf("sip:%s", user);
2235 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2236 if(pbuddy){
2237 alias = purple_buddy_get_local_alias(pbuddy);
2238 uri_alias = g_strdup_printf("sip:%s", alias);
2239 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2240 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2241 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2242 purple_blist_alias_buddy(pbuddy, display_name);
2244 g_free(uri_alias);
2246 g_free(uri_user);
2248 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2249 if(!g_ascii_strcasecmp(acknowledged,"false")){
2250 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2251 hdr = g_strdup_printf(
2252 "Contact: %s\r\n"
2253 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2255 body = g_strdup_printf(
2256 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2257 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2258 "</setSubscribers>", user);
2260 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2261 g_free(body);
2262 g_free(hdr);
2266 g_free(to);
2267 g_free(contact);
2268 xmlnode_free(xml);
2271 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2273 gchar *to = g_strdup_printf("sip:%s", sip->username);
2274 gchar *tmp = get_contact(sip);
2275 gchar *hdr = g_strdup_printf(
2276 "Event: vnd-microsoft-roaming-ACL\r\n"
2277 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2278 "Supported: com.microsoft.autoextend\r\n"
2279 "Supported: ms-benotify\r\n"
2280 "Proxy-Require: ms-benotify\r\n"
2281 "Supported: ms-piggyback-first-notify\r\n"
2282 "Contact: %s\r\n", tmp);
2283 g_free(tmp);
2285 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2286 g_free(to);
2287 g_free(hdr);
2291 * To request for presence information about the user, access level settings that have already been configured by the user
2292 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2293 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2296 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2298 gchar *to = g_strdup_printf("sip:%s", sip->username);
2299 gchar *tmp = get_contact(sip);
2300 gchar *hdr = g_strdup_printf(
2301 "Event: vnd-microsoft-roaming-self\r\n"
2302 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2303 "Supported: ms-benotify\r\n"
2304 "Proxy-Require: ms-benotify\r\n"
2305 "Supported: ms-piggyback-first-notify\r\n"
2306 "Contact: %s\r\n"
2307 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2309 gchar *body=g_strdup(
2310 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2311 "<roaming type=\"categories\"/>"
2312 "<roaming type=\"containers\"/>"
2313 "<roaming type=\"subscribers\"/></roamingList>");
2315 g_free(tmp);
2316 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2317 g_free(body);
2318 g_free(to);
2319 g_free(hdr);
2323 * For 2005 version
2325 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2327 gchar *to = g_strdup_printf("sip:%s", sip->username);
2328 gchar *tmp = get_contact(sip);
2329 gchar *hdr = g_strdup_printf(
2330 "Event: vnd-microsoft-provisioning\r\n"
2331 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2332 "Supported: com.microsoft.autoextend\r\n"
2333 "Supported: ms-benotify\r\n"
2334 "Proxy-Require: ms-benotify\r\n"
2335 "Supported: ms-piggyback-first-notify\r\n"
2336 "Expires: 0\r\n"
2337 "Contact: %s\r\n", tmp);
2339 g_free(tmp);
2340 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2341 g_free(to);
2342 g_free(hdr);
2345 /** Subscription for provisioning information to help with initial
2346 * configuration. This subscription is a one-time query (denoted by the Expires header,
2347 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2348 * configuration, meeting policies, and policy settings that Communicator must enforce.
2349 * TODO: for what we need this information.
2352 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2354 gchar *to = g_strdup_printf("sip:%s", sip->username);
2355 gchar *tmp = get_contact(sip);
2356 gchar *hdr = g_strdup_printf(
2357 "Event: vnd-microsoft-provisioning-v2\r\n"
2358 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2359 "Supported: com.microsoft.autoextend\r\n"
2360 "Supported: ms-benotify\r\n"
2361 "Proxy-Require: ms-benotify\r\n"
2362 "Supported: ms-piggyback-first-notify\r\n"
2363 "Expires: 0\r\n"
2364 "Contact: %s\r\n"
2365 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2366 gchar *body = g_strdup(
2367 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2368 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2369 "<provisioningGroup name=\"ucPolicy\"/>"
2370 "</provisioningGroupList>");
2372 g_free(tmp);
2373 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2374 g_free(body);
2375 g_free(to);
2376 g_free(hdr);
2379 /* IM Session (INVITE and MESSAGE methods) */
2381 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2383 struct sip_im_session *session;
2384 GSList *entry;
2385 if (sip == NULL || who == NULL) {
2386 return NULL;
2389 entry = sip->im_sessions;
2390 while (entry) {
2391 session = entry->data;
2392 if ((who != NULL && !strcmp(who, session->with))) {
2393 return session;
2395 entry = entry->next;
2397 return NULL;
2400 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2402 struct sip_im_session *session = find_im_session(sip, who);
2403 if (!session) {
2404 session = g_new0(struct sip_im_session, 1);
2405 session->with = g_strdup(who);
2406 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2407 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2409 return session;
2412 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2414 struct sip_dialog *dialog = session->dialog;
2415 GSList *entry;
2417 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2419 if (dialog) {
2420 entry = dialog->routes;
2421 while (entry) {
2422 g_free(entry->data);
2423 entry = g_slist_remove(entry, entry->data);
2425 entry = dialog->supported;
2426 while (entry) {
2427 g_free(entry->data);
2428 entry = g_slist_remove(entry, entry->data);
2430 g_free(dialog->callid);
2431 g_free(dialog->ourtag);
2432 g_free(dialog->theirtag);
2433 g_free(dialog->theirepid);
2434 g_free(dialog->request);
2436 g_free(session->dialog);
2438 entry = session->outgoing_message_queue;
2439 while (entry) {
2440 g_free(entry->data);
2441 entry = g_slist_remove(entry, entry->data);
2444 g_hash_table_destroy(session->unconfirmed_messages);
2446 g_free(session->with);
2447 g_free(session);
2450 static gboolean
2451 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2453 gboolean ret = TRUE;
2455 if (msg->response != 200) {
2456 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2457 return FALSE;
2460 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2462 return ret;
2466 * Asks UA/proxy about its capabilities.
2468 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2470 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2471 gchar *contact = get_contact(sip);
2472 gchar *request;
2473 request = g_strdup_printf(
2474 "Accept: application/sdp\r\n"
2475 "Contact: %s\r\n", contact);
2477 g_free(contact);
2479 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2481 g_free(to);
2482 g_free(request);
2485 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2487 char *msg, *msg_tmp;
2488 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2489 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2490 g_free(msg_tmp);
2491 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2492 "possibly because one or more persons are offline:\n%s") ,
2493 msg ? msg : "");
2494 purple_conv_present_error(with, sip->account, msg_tmp);
2495 g_free(msg);
2496 g_free(msg_tmp);
2499 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2501 static gboolean
2502 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2504 gboolean ret = TRUE;
2505 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2506 struct sip_im_session * session = find_im_session(sip, with);
2507 struct sip_dialog *dialog;
2508 gchar *cseq;
2509 char *key;
2510 gchar *message;
2512 if (!session) {
2513 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2514 g_free(with);
2515 return FALSE;
2518 dialog = session->dialog;
2519 if (!dialog) {
2520 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2521 g_free(with);
2522 return FALSE;
2525 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2526 key = g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq));
2527 g_free(cseq);
2528 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2530 if (msg->response != 200) {
2531 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2533 sipe_present_message_undelivered_err(with, sip, message);
2534 im_session_destroy(sip, session);
2535 ret = FALSE;
2536 } else {
2537 g_hash_table_remove(session->unconfirmed_messages, key);
2538 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2539 key, g_hash_table_size(session->unconfirmed_messages));
2542 g_free(key);
2543 g_free(with);
2545 sipe_im_process_queue(sip, session);
2546 return ret;
2549 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2551 gchar *hdr;
2552 gchar *fullto;
2553 gchar *tmp;
2554 char *msgformat;
2555 char *msgtext;
2556 gchar *msgr_value;
2557 gchar *msgr;
2559 if (strncmp("sip:", session->with, 4)) {
2560 fullto = g_strdup_printf("sip:%s", session->with);
2561 } else {
2562 fullto = g_strdup(session->with);
2565 sipe_parse_html(msg, &msgformat, &msgtext);
2566 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2568 msgr_value = sipmsg_get_msgr_string(msgformat);
2569 g_free(msgformat);
2570 if (msgr_value) {
2571 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2572 g_free(msgr_value);
2573 } else {
2574 msgr = g_strdup("");
2577 tmp = get_contact(sip);
2578 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2579 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2580 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2581 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2582 tmp, msgr);
2583 g_free(tmp);
2584 g_free(msgr);
2586 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2587 g_free(msgtext);
2588 g_free(hdr);
2589 g_free(fullto);
2593 static void
2594 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2596 GSList *entry = session->outgoing_message_queue;
2598 if (session->outgoing_invite) return; //do not send messages until INVITE responded.
2600 while (entry) {
2601 char *key = g_strdup_printf("<%s><%d><MESSAGE>", session->dialog->callid, (session->dialog->cseq) + 1);
2602 char *queued_msg = entry->data;
2603 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2604 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2605 key, g_hash_table_size(session->unconfirmed_messages));
2606 g_free(key);
2607 sipe_send_message(sip, session, queued_msg);
2608 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2609 g_free(queued_msg);
2613 static void
2614 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2616 GSList *hdr = msg->headers;
2617 struct siphdrelement *elem;
2618 gchar *contact;
2620 while(hdr)
2622 elem = hdr->data;
2623 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2625 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2626 dialog->routes = g_slist_append(dialog->routes, route);
2628 hdr = g_slist_next(hdr);
2631 if (outgoing)
2633 dialog->routes = g_slist_reverse(dialog->routes);
2636 if (dialog->routes)
2638 dialog->request = dialog->routes->data;
2639 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2642 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2643 dialog->routes = g_slist_append(dialog->routes, contact);
2646 static void
2647 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2649 GSList *hdr = msg->headers;
2650 struct siphdrelement *elem;
2651 while(hdr)
2653 elem = hdr->data;
2654 if(!g_ascii_strcasecmp(elem->name, "Supported")
2655 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2657 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2660 hdr = g_slist_next(hdr);
2664 static void
2665 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2667 gchar *us = outgoing ? "From" : "To";
2668 gchar *them = outgoing ? "To" : "From";
2670 g_free(dialog->callid);
2671 g_free(dialog->ourtag);
2672 g_free(dialog->theirtag);
2674 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2675 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2676 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2677 if (!dialog->theirepid) {
2678 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2679 if (!dialog->theirepid) {
2680 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2684 sipe_get_route_header(msg, dialog, outgoing);
2685 sipe_get_supported_header(msg, dialog, outgoing);
2689 static gboolean
2690 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2692 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2693 struct sip_im_session * session = find_im_session(sip, with);
2694 struct sip_dialog *dialog;
2695 char *cseq;
2696 char *key;
2697 gchar *message;
2699 if (!session) {
2700 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2701 g_free(with);
2702 return FALSE;
2705 dialog = session->dialog;
2706 if (!dialog) {
2707 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2708 g_free(with);
2709 return FALSE;
2712 sipe_parse_dialog(msg, dialog, TRUE);
2714 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2715 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2716 g_free(cseq);
2717 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2719 if (msg->response != 200) {
2720 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2722 sipe_present_message_undelivered_err(with, sip, message);
2723 im_session_destroy(sip, session);
2724 g_free(with);
2725 return FALSE;
2728 dialog->cseq = 0;
2729 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2730 session->outgoing_invite = NULL;
2731 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2732 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2733 if (session->outgoing_message_queue) {
2734 char *queued_msg = session->outgoing_message_queue->data;
2735 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2736 g_free(queued_msg);
2740 sipe_im_process_queue(sip, session);
2742 g_hash_table_remove(session->unconfirmed_messages, key);
2743 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2744 key, g_hash_table_size(session->unconfirmed_messages));
2746 g_free(key);
2747 g_free(with);
2748 return TRUE;
2752 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session *session, const gchar *msg_body)
2754 gchar *hdr;
2755 gchar *to;
2756 gchar *from;
2757 gchar *contact;
2758 gchar *body;
2759 char *msgformat;
2760 char *msgtext;
2761 char *base64_msg;
2762 char *ms_text_format;
2763 gchar *msgr_value;
2764 gchar *msgr;
2765 char *key;
2767 if (session->dialog) {
2768 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2769 return;
2772 session->dialog = g_new0(struct sip_dialog, 1);
2773 session->dialog->callid = gencallid();
2775 if (strstr(session->with, "sip:")) {
2776 to = g_strdup(session->with);
2777 } else {
2778 to = g_strdup_printf("sip:%s", session->with);
2781 sipe_parse_html(msg_body, &msgformat, &msgtext);
2782 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2784 msgr_value = sipmsg_get_msgr_string(msgformat);
2785 g_free(msgformat);
2786 msgr = "";
2787 if (msgr_value) {
2788 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2789 g_free(msgr_value);
2792 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2793 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2794 g_free(msgtext);
2795 g_free(msgr);
2796 g_free(base64_msg);
2798 key = g_strdup_printf("<%s><%d><INVITE>", session->dialog->callid, (session->dialog->cseq) + 1);
2799 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2800 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2801 key, g_hash_table_size(session->unconfirmed_messages));
2802 g_free(key);
2804 contact = get_contact(sip);
2805 /* from = g_strdup_printf("sip:%s", sip->username);*/
2806 hdr = g_strdup_printf(
2807 /*"Supported: ms-delayed-accept\r\n"*/
2808 /*"Roster-Manager: <%s>\r\n"*/
2809 /*"EndPoints: <%s>, <%s>\r\n"*/
2810 /*"Supported: com.microsoft.rtc-multiparty\r\n"*/
2811 "Contact: %s\r\n%s"
2812 "Content-Type: application/sdp\r\n",
2813 contact, ms_text_format);
2814 g_free(ms_text_format);
2816 body = g_strdup_printf(
2817 "v=0\r\n"
2818 "o=- 0 0 IN IP4 %s\r\n"
2819 "s=session\r\n"
2820 "c=IN IP4 %s\r\n"
2821 "t=0 0\r\n"
2822 "m=message %d sip null\r\n"
2823 "a=accept-types:text/plain text/html image/gif "
2824 "multipart/alternative application/im-iscomposing+xml\r\n",
2825 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2827 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2828 to, to, hdr, body, session->dialog, process_invite_response);
2830 g_free(to);
2831 /* g_free(from);*/
2832 g_free(body);
2833 g_free(hdr);
2834 g_free(contact);
2837 static void
2838 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2840 if (session) {
2841 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2842 im_session_destroy(sip, session);
2846 static void
2847 sipe_convo_closed(PurpleConnection * gc, const char *who)
2849 struct sipe_account_data *sip = gc->proto_data;
2851 purple_debug_info("sipe", "conversation with %s closed\n", who);
2852 im_session_close(sip, find_im_session(sip, who));
2855 static void
2856 im_session_close_all (struct sipe_account_data *sip)
2858 GSList *entry = sip->im_sessions;
2859 while (entry) {
2860 im_session_close (sip, entry->data);
2861 entry = sip->im_sessions;
2865 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2867 struct sipe_account_data *sip;
2868 gchar *to;
2869 struct sip_im_session *session;
2871 sip = gc->proto_data;
2872 to = g_strdup(who);
2874 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2876 session = find_or_create_im_session(sip, who);
2878 // Queue the message
2879 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
2881 if (session->dialog && session->dialog->callid) {
2882 sipe_im_process_queue(sip, session);
2883 } else if (!session->outgoing_invite) {
2884 // Need to send the INVITE to get the outgoing dialog setup
2885 sipe_invite(sip, session, what);
2888 g_free(to);
2889 return 1;
2892 /* End IM Session (INVITE and MESSAGE methods) */
2894 static unsigned int
2895 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2897 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2898 struct sip_im_session *session;
2900 if (state == PURPLE_NOT_TYPING)
2901 return 0;
2903 session = find_im_session(sip, who);
2905 if (session && session->dialog) {
2906 send_sip_request(gc, "INFO", who, who,
2907 "Content-Type: application/xml\r\n",
2908 SIPE_SEND_TYPING, session->dialog, NULL);
2910 return SIPE_TYPING_SEND_TIMEOUT;
2913 static gboolean resend_timeout(struct sipe_account_data *sip)
2915 GSList *tmp = sip->transactions;
2916 time_t currtime = time(NULL);
2917 while (tmp) {
2918 struct transaction *trans = tmp->data;
2919 tmp = tmp->next;
2920 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2921 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2922 /* TODO 408 */
2923 } else {
2924 if ((currtime - trans->time > 2) && trans->retries == 0) {
2925 trans->retries++;
2926 sendout_sipmsg(sip, trans->msg);
2930 return TRUE;
2933 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2935 /* register again when security token expires */
2936 /* we have to start a new authentication as the security token
2937 * is almost expired by sending a not signed REGISTER message */
2938 purple_debug_info("sipe", "do a full reauthentication\n");
2939 sipe_auth_free(&sip->registrar);
2940 sip->registerstatus = 0;
2941 do_register(sip);
2942 sip->reauthenticate_set = FALSE;
2945 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2947 gchar *from;
2948 gchar *contenttype;
2949 gboolean found = FALSE;
2951 from = parse_from(sipmsg_find_header(msg, "From"));
2953 if (!from) return;
2955 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2957 contenttype = sipmsg_find_header(msg, "Content-Type");
2958 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2960 gchar *html = get_html_message(contenttype, msg->body);
2961 serv_got_im(sip->gc, from, html, 0, time(NULL));
2962 g_free(html);
2963 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2964 found = TRUE;
2966 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2967 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2968 xmlnode *state;
2969 gchar *statedata;
2971 if (!isc) {
2972 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2973 return;
2976 state = xmlnode_get_child(isc, "state");
2978 if (!state) {
2979 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2980 xmlnode_free(isc);
2981 return;
2984 statedata = xmlnode_get_data(state);
2985 if (statedata) {
2986 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2987 else serv_got_typing_stopped(sip->gc, from);
2989 g_free(statedata);
2991 xmlnode_free(isc);
2992 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2993 found = TRUE;
2995 if (!found) {
2996 purple_debug_info("sipe", "got unknown mime-type");
2997 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
2999 g_free(from);
3002 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3004 gchar *ms_text_format;
3005 gchar *from;
3006 gchar *body;
3007 struct sip_im_session *session;
3009 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3011 // Only accept text invitations
3012 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3013 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3014 return;
3017 from = parse_from(sipmsg_find_header(msg, "From"));
3018 session = find_or_create_im_session (sip, from);
3019 if (session) {
3020 if (session->dialog) {
3021 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3022 } else {
3023 session->dialog = g_new0(struct sip_dialog, 1);
3025 sipe_parse_dialog(msg, session->dialog, FALSE);
3027 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3028 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3029 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3030 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3032 } else {
3033 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3036 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3037 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3038 if (ms_text_format) {
3039 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3041 gchar *html = get_html_message(ms_text_format, NULL);
3042 if (html) {
3043 serv_got_im(sip->gc, from, html, 0, time(NULL));
3044 g_free(html);
3045 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3049 g_free(from);
3051 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3052 sipmsg_remove_header(msg, "Ms-Text-Format");
3053 sipmsg_remove_header(msg, "EndPoints");
3054 sipmsg_remove_header(msg, "User-Agent");
3055 sipmsg_remove_header(msg, "Roster-Manager");
3057 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3058 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3060 body = g_strdup_printf(
3061 "v=0\r\n"
3062 "o=- 0 0 IN IP4 %s\r\n"
3063 "s=session\r\n"
3064 "c=IN IP4 %s\r\n"
3065 "t=0 0\r\n"
3066 "m=message %d sip sip:%s\r\n"
3067 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3068 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3069 sip->realport, sip->username);
3070 send_sip_response(sip->gc, msg, 200, "OK", body);
3071 g_free(body);
3074 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3076 gchar *body;
3078 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3079 sipmsg_remove_header(msg, "EndPoints");
3080 sipmsg_remove_header(msg, "User-Agent");
3082 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3083 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3085 body = g_strdup_printf(
3086 "v=0\r\n"
3087 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3088 "s=session\r\n"
3089 "c=IN IP4 0.0.0.0\r\n"
3090 "t=0 0\r\n"
3091 "m=message %d sip sip:%s\r\n"
3092 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3093 sip->realport, sip->username);
3094 send_sip_response(sip->gc, msg, 200, "OK", body);
3095 g_free(body);
3098 static void sipe_connection_cleanup(struct sipe_account_data *);
3099 static void create_connection(struct sipe_account_data *, gchar *, int);
3101 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3103 gchar *tmp;
3104 const gchar *expires_header;
3105 int expires, i;
3106 GSList *hdr = msg->headers;
3107 GSList *entry;
3108 struct siphdrelement *elem;
3110 expires_header = sipmsg_find_header(msg, "Expires");
3111 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3112 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3114 switch (msg->response) {
3115 case 200:
3116 if (expires == 0) {
3117 sip->registerstatus = 0;
3118 } else {
3119 gchar *contact_hdr = NULL;
3120 gchar *gruu = NULL;
3121 gchar *epid;
3122 gchar *uuid;
3123 gchar *timeout;
3125 if (!sip->reregister_set) {
3126 gchar *action_name = g_strdup_printf("<%s>", "registration");
3127 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
3128 g_free(action_name);
3129 sip->reregister_set = TRUE;
3132 sip->registerstatus = 3;
3134 if (!sip->reauthenticate_set) {
3135 /* we have to reauthenticate as our security token expires
3136 after eight hours (be five minutes early) */
3137 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3138 guint reauth_timeout = (8 * 3600) - 360;
3139 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3140 g_free(action_name);
3141 sip->reauthenticate_set = TRUE;
3144 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3146 epid = get_epid(sip);
3147 uuid = generateUUIDfromEPID(epid);
3148 g_free(epid);
3150 // There can be multiple Contact headers (one per location where the user is logged in) so
3151 // make sure to only get the one for this uuid
3152 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3153 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3154 if (valid_contact) {
3155 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3156 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3157 g_free(valid_contact);
3158 break;
3159 } else {
3160 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3163 g_free(uuid);
3165 g_free(sip->contact);
3166 if(gruu) {
3167 sip->contact = g_strdup_printf("<%s>", gruu);
3168 g_free(gruu);
3169 } else {
3170 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3171 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);
3173 sip->msrtc_event_categories = FALSE;
3174 sip->batched_support = FALSE;
3176 while(hdr)
3178 elem = hdr->data;
3179 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3180 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3181 sip->msrtc_event_categories = TRUE;
3182 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3184 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3185 sip->batched_support = TRUE;
3186 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3189 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3190 gchar **caps = g_strsplit(elem->value,",",0);
3191 i = 0;
3192 while (caps[i]) {
3193 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3194 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3195 i++;
3197 g_strfreev(caps);
3199 hdr = g_slist_next(hdr);
3202 if (!sip->subscribed) { //do it just once, not every re-register
3203 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3204 sipe_options_request(sip, sip->sipdomain);
3206 entry = sip->allow_events;
3207 while (entry) {
3208 tmp = entry->data;
3209 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3210 sipe_subscribe_roaming_contacts(sip, msg);
3212 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3213 sipe_subscribe_roaming_acl(sip, msg);
3215 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3216 sipe_subscribe_roaming_self(sip, msg);
3218 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3219 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3220 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3221 sipe_subscribe_roaming_provisioning(sip, msg);
3223 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3224 sipe_subscribe_presence_wpending(sip, msg);
3226 entry = entry->next;
3228 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3229 sip->subscribed = TRUE;
3232 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3233 "timeout=", ";", NULL);
3234 if (timeout != NULL) {
3235 sscanf(timeout, "%u", &sip->keepalive_timeout);
3236 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3237 sip->keepalive_timeout);
3238 g_free(timeout);
3239 } else {
3240 sip->keepalive_timeout = 300;
3243 // Should we remove the transaction here?
3244 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3245 transactions_remove(sip, tc);
3247 break;
3248 case 301:
3250 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3252 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3253 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3254 gchar **tmp;
3255 gchar *hostname;
3256 int port = 0;
3257 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3258 int i = 1;
3260 tmp = g_strsplit(parts[0], ":", 0);
3261 hostname = g_strdup(tmp[0]);
3262 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3263 g_strfreev(tmp);
3265 while (parts[i]) {
3266 tmp = g_strsplit(parts[i], "=", 0);
3267 if (tmp[1]) {
3268 if (g_strcasecmp("transport", tmp[0]) == 0) {
3269 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3270 transport = SIPE_TRANSPORT_TCP;
3271 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3272 transport = SIPE_TRANSPORT_UDP;
3276 g_strfreev(tmp);
3277 i++;
3279 g_strfreev(parts);
3281 /* Close old connection */
3282 sipe_connection_cleanup(sip);
3284 /* Create new connection */
3285 sip->transport = transport;
3286 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3287 hostname, port, TRANSPORT_DESCRIPTOR);
3288 create_connection(sip, hostname, port);
3290 g_free(redirect);
3292 break;
3293 case 401:
3294 if (sip->registerstatus != 2) {
3295 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3296 if (sip->registrar.retries > 3) {
3297 sip->gc->wants_to_die = TRUE;
3298 purple_connection_error(sip->gc, _("Wrong Password"));
3299 return TRUE;
3301 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3302 tmp = sipmsg_find_auth_header(msg, "NTLM");
3303 } else {
3304 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3306 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3307 fill_auth(sip, tmp, &sip->registrar);
3308 sip->registerstatus = 2;
3309 if (sip->account->disconnecting) {
3310 do_register_exp(sip, 0);
3311 } else {
3312 do_register(sip);
3315 break;
3316 case 403:
3318 gchar *warning = sipmsg_find_header(msg, "Warning");
3319 if (warning != NULL) {
3320 /* Example header:
3321 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3323 gchar **tmp = g_strsplit(warning, "\"", 0);
3324 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3325 g_strfreev(tmp);
3326 } else {
3327 warning = g_strdup(_("You have been rejected by the server"));
3330 sip->gc->wants_to_die = TRUE;
3331 purple_connection_error(sip->gc, warning);
3332 g_free(warning);
3333 return TRUE;
3335 break;
3336 case 404:
3338 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3339 if (warning != NULL) {
3340 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3341 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3342 g_free(reason);
3343 } else {
3344 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3347 sip->gc->wants_to_die = TRUE;
3348 purple_connection_error(sip->gc, warning);
3349 g_free(warning);
3350 return TRUE;
3352 break;
3353 case 503:
3355 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3356 if (warning != NULL) {
3357 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3358 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3359 g_free(reason);
3360 } else {
3361 warning = g_strdup(_("Service unavailable: no reason given"));
3364 sip->gc->wants_to_die = TRUE;
3365 purple_connection_error(sip->gc, warning);
3366 g_free(warning);
3367 return TRUE;
3369 break;
3371 return TRUE;
3374 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3376 const char *uri;
3377 xmlnode *xn_categories;
3378 xmlnode *xn_category;
3379 xmlnode *xn_node;
3380 const char *activity = NULL;
3382 xn_categories = xmlnode_from_str(data, len);
3383 uri = xmlnode_get_attrib(xn_categories, "uri");
3385 for (xn_category = xmlnode_get_child(xn_categories, "category");
3386 xn_category ;
3387 xn_category = xmlnode_get_next_twin(xn_category) )
3389 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3391 if (!strcmp(attrVar, "note"))
3393 char *note;
3394 struct sipe_buddy *sbuddy;
3395 xn_node = xmlnode_get_child(xn_category, "note");
3396 if (!xn_node) continue;
3397 xn_node = xmlnode_get_child(xn_node, "body");
3398 if (!xn_node) continue;
3400 note = xmlnode_get_data(xn_node);
3402 if(uri){
3403 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3405 if (sbuddy && note)
3407 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note);
3408 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3409 sbuddy->annotation = g_strdup(note);
3411 if(note)
3412 g_free(note);
3414 else if(!strcmp(attrVar, "state"))
3416 char *data;
3417 int avail;
3418 xn_node = xmlnode_get_child(xn_category, "state");
3419 if (!xn_node) continue;
3420 xn_node = xmlnode_get_child(xn_node, "availability");
3421 if (!xn_node) continue;
3423 data = xmlnode_get_data(xn_node);
3424 avail = atoi(data);
3425 g_free(data);
3427 if (avail < 3000)
3428 activity = SIPE_STATUS_ID_UNKNOWN;
3429 else if (avail < 4500)
3430 activity = SIPE_STATUS_ID_AVAILABLE;
3431 else if (avail < 6000)
3432 activity = SIPE_STATUS_ID_BRB;
3433 else if (avail < 7500)
3434 activity = SIPE_STATUS_ID_ONPHONE;
3435 else if (avail < 9000)
3436 activity = SIPE_STATUS_ID_BUSY;
3437 else if (avail < 12000)
3438 activity = SIPE_STATUS_ID_DND;
3439 else if (avail < 18000)
3440 activity = SIPE_STATUS_ID_AWAY;
3441 else
3442 activity = SIPE_STATUS_ID_OFFLINE;
3445 if(activity) {
3446 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
3447 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3450 xmlnode_free(xn_categories);
3453 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3455 const char *uri,*state;
3456 xmlnode *xn_list;
3457 xmlnode *xn_resource;
3458 xmlnode *xn_instance;
3460 xn_list = xmlnode_from_str(data, len);
3462 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3463 xn_resource;
3464 xn_resource = xmlnode_get_next_twin(xn_resource) )
3466 struct sipe_buddy *sbuddy;
3467 uri = xmlnode_get_attrib(xn_resource, "uri");
3468 xn_instance = xmlnode_get_child(xn_resource, "instance");
3469 if (!xn_instance) return;
3471 state = xmlnode_get_attrib(xn_instance, "state");
3472 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3473 if(strstr(state,"resubscribe")){
3474 sipe_subscribe_presence_single(sip, uri);
3475 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3476 if(sbuddy){
3477 sbuddy->resubscribed = TRUE;
3484 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3486 const gchar *uri;
3487 gchar *getbasic;
3488 gchar *activity = NULL;
3489 xmlnode *pidf;
3490 xmlnode *basicstatus = NULL, *tuple, *status;
3491 gboolean isonline = FALSE;
3492 xmlnode *display_name_node;
3494 pidf = xmlnode_from_str(data, len);
3495 if (!pidf) {
3496 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3497 return;
3500 uri = xmlnode_get_attrib(pidf, "entity");
3502 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3504 if ((status = xmlnode_get_child(tuple, "status"))) {
3505 basicstatus = xmlnode_get_child(status, "basic");
3509 if (!basicstatus) {
3510 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3511 xmlnode_free(pidf);
3512 return;
3515 getbasic = xmlnode_get_data(basicstatus);
3516 if (!getbasic) {
3517 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3518 xmlnode_free(pidf);
3519 return;
3522 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3523 if (strstr(getbasic, "open")) {
3524 isonline = TRUE;
3526 g_free(getbasic);
3528 display_name_node = xmlnode_get_child(pidf, "display-name");
3529 // updating display name if alias was just URI
3530 if (display_name_node) {
3531 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3532 GSList *entry = buddies;
3533 PurpleBuddy *p_buddy;
3534 char * display_name = xmlnode_get_data(display_name_node);
3536 while (entry) {
3537 const char *server_alias;
3538 char *alias;
3540 p_buddy = entry->data;
3542 alias = (char *)purple_buddy_get_alias(p_buddy);
3543 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3544 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3545 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3546 purple_blist_alias_buddy(p_buddy, display_name);
3548 g_free(alias);
3550 server_alias = purple_buddy_get_server_alias(p_buddy);
3551 if (display_name &&
3552 ( (server_alias && strcmp(display_name, server_alias))
3553 || !server_alias || strlen(server_alias) == 0 )
3555 purple_blist_server_alias_buddy(p_buddy, display_name);
3558 entry = entry->next;
3560 g_free(display_name);
3563 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3564 if ((status = xmlnode_get_child(tuple, "status"))) {
3565 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3566 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3567 activity = xmlnode_get_data(basicstatus);
3568 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3574 if (isonline) {
3575 const gchar * status_id = NULL;
3576 if (activity) {
3577 if (strstr(activity, "busy")) {
3578 status_id = SIPE_STATUS_ID_BUSY;
3579 } else if (strstr(activity, "away")) {
3580 status_id = SIPE_STATUS_ID_AWAY;
3584 if (!status_id) {
3585 status_id = SIPE_STATUS_ID_AVAILABLE;
3588 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3589 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3590 } else {
3591 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
3594 g_free(activity);
3595 xmlnode_free(pidf);
3598 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3600 const char *availability;
3601 const char *activity;
3602 const char *display_name = NULL;
3603 const char *activity_name = NULL;
3604 const char *name;
3605 char *uri;
3606 int avl;
3607 int act;
3608 struct sipe_buddy *sbuddy;
3610 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3612 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3613 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3614 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3615 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3616 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3617 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3618 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
3619 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
3621 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3622 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3623 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3624 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3625 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3626 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3628 name = xmlnode_get_attrib(xn_presentity, "uri");
3629 uri = g_strdup_printf("sip:%s", name);
3630 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3631 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3633 // updating display name if alias was just URI
3634 if (xn_display_name) {
3635 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3636 GSList *entry = buddies;
3637 PurpleBuddy *p_buddy;
3638 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3640 while (entry) {
3641 const char *email_str, *server_alias;
3643 p_buddy = entry->data;
3645 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3646 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3647 purple_blist_alias_buddy(p_buddy, display_name);
3650 server_alias = purple_buddy_get_server_alias(p_buddy);
3651 if (display_name &&
3652 ( (server_alias && strcmp(display_name, server_alias))
3653 || !server_alias || strlen(server_alias) == 0 )
3655 purple_blist_server_alias_buddy(p_buddy, display_name);
3658 if (email) {
3659 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3660 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3661 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3665 entry = entry->next;
3669 avl = atoi(availability);
3670 act = atoi(activity);
3672 if(sip->msrtc_event_categories){
3673 if (act == 100 && avl == 0)
3674 activity_name = SIPE_STATUS_ID_OFFLINE;
3675 else if (act == 100 && avl == 300)
3676 activity_name = SIPE_STATUS_ID_AWAY;
3677 else if (act == 300 && avl == 300)
3678 activity_name = SIPE_STATUS_ID_BRB;
3679 else if (act == 400 && avl == 300)
3680 activity_name = SIPE_STATUS_ID_AVAILABLE;
3681 else if (act == 500 && act == 300)
3682 activity_name = SIPE_STATUS_ID_ONPHONE;
3683 else if (act == 600 && avl == 300)
3684 activity_name = SIPE_STATUS_ID_BUSY;
3685 else if (act == 0 && avl == 0){ //MSRTC elements are zero
3686 if(avail){ //Check for LegacyInterop elements
3687 avl = atoi(avail);
3688 if(avl == 18500)
3689 activity_name = SIPE_STATUS_ID_OFFLINE;
3690 else if (avl == 3500)
3691 activity_name = SIPE_STATUS_ID_AVAILABLE;
3692 else if (avl == 15500)
3693 activity_name = SIPE_STATUS_ID_AWAY;
3694 else if (avl == 6500)
3695 activity_name = SIPE_STATUS_ID_BUSY;
3696 else if (avl == 12500)
3697 activity_name = SIPE_STATUS_ID_BRB;
3702 if(activity_name == NULL){
3703 if (act <= 100)
3704 activity_name = SIPE_STATUS_ID_AWAY;
3705 else if (act <= 150)
3706 activity_name = SIPE_STATUS_ID_LUNCH;
3707 else if (act <= 300)
3708 activity_name = SIPE_STATUS_ID_BRB;
3709 else if (act <= 400)
3710 activity_name = SIPE_STATUS_ID_AVAILABLE;
3711 else if (act <= 500)
3712 activity_name = SIPE_STATUS_ID_ONPHONE;
3713 else if (act <= 600)
3714 activity_name = SIPE_STATUS_ID_BUSY;
3715 else
3716 activity_name = SIPE_STATUS_ID_AVAILABLE;
3718 if (avl == 0)
3719 activity_name = SIPE_STATUS_ID_OFFLINE;
3722 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3723 if (sbuddy)
3725 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3726 sbuddy->annotation = NULL;
3727 if (note) { sbuddy->annotation = g_strdup(note); }
3729 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3730 sbuddy->device_name = NULL;
3731 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3734 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3735 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3736 g_free(note);
3737 xmlnode_free(xn_presentity);
3738 g_free(uri);
3741 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3743 char *ctype = sipmsg_find_header(msg, "Content-Type");
3745 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3747 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3748 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3750 const char *content = msg->body;
3751 unsigned length = msg->bodylen;
3752 PurpleMimeDocument *mime = NULL;
3754 if (strstr(ctype, "multipart"))
3756 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3757 const char *content_type;
3758 GList* parts;
3759 mime = purple_mime_document_parse(doc);
3760 parts = purple_mime_document_get_parts(mime);
3761 while(parts) {
3762 content = purple_mime_part_get_data(parts->data);
3763 length = purple_mime_part_get_length(parts->data);
3764 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3765 if(content_type && strstr(content_type,"application/rlmi+xml"))
3767 process_incoming_notify_rlmi_resub(sip, content, length);
3769 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3771 process_incoming_notify_msrtc(sip, content, length);
3773 else
3775 process_incoming_notify_rlmi(sip, content, length);
3777 parts = parts->next;
3779 g_free(doc);
3781 if (mime)
3783 purple_mime_document_free(mime);
3786 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3788 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3790 else if(strstr(ctype, "application/rlmi+xml"))
3792 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3795 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3797 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3799 else
3801 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3806 * Dispatcher for all incoming subscription information
3807 * whether it comes from NOTIFY, BENOTIFY requests or
3808 * piggy-backed to subscription's OK responce.
3810 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3811 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3813 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3815 gchar *event = sipmsg_find_header(msg, "Event");
3816 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3817 int timeout = 0;
3819 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3820 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3822 if (!request)
3824 const gchar *expires_header;
3825 expires_header = sipmsg_find_header(msg, "Expires");
3826 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3827 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
3828 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
3831 if (!subscription_state || strstr(subscription_state, "active"))
3833 if (event && !g_ascii_strcasecmp(event, "presence"))
3835 sipe_process_presence(sip, msg);
3837 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3839 sipe_process_roaming_contacts(sip, msg, NULL);
3841 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
3843 sipe_process_roaming_self(sip, msg);
3845 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3847 sipe_process_roaming_acl(sip, msg);
3849 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3851 sipe_process_presence_wpending(sip, msg);
3853 else
3855 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3859 //The server sends a (BE)NOTIFY with the status 'terminated'
3860 if(request && subscription_state && strstr(subscription_state, "terminated") ) {
3861 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3862 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3863 g_free(from);
3866 if (timeout && event) {// For LSC 2005 and OCS 2007
3867 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
3868 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
3870 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
3871 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_roaming_contacts, sip, msg);
3872 g_free(action_name);
3874 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
3875 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
3877 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
3878 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_roaming_acl, sip, msg);
3879 g_free(action_name);
3881 else*/
3882 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
3883 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3885 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3886 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_wpending, sip, NULL);
3887 g_free(action_name);
3889 else if (!g_ascii_strcasecmp(event, "presence") &&
3890 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3892 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3893 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", who);
3894 if(sip->batched_support){
3895 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
3896 if(!g_ascii_strcasecmp(who, my_self)){
3897 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_batched, sip, NULL);
3898 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
3900 else{
3901 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_single, sip, who);
3902 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who,timeout);
3904 g_free(my_self);
3906 else{
3907 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_single, sip, who);
3908 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
3910 g_free(action_name);
3911 /* "who" will be freed by the action we just scheduled */
3915 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
3917 sipe_process_registration_notify(sip, msg);
3920 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3921 if (request && !benotify)
3923 sipmsg_remove_header(msg, "Expires");
3924 sipmsg_remove_header(msg, "subscription-state");
3925 sipmsg_remove_header(msg, "Event");
3926 sipmsg_remove_header(msg, "Require");
3927 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3932 * unused. Needed?
3934 static gchar* gen_xpidf(struct sipe_account_data *sip)
3936 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3937 "<presence>\r\n"
3938 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3939 "<display name=\"sip:%s\"/>\r\n"
3940 "<atom id=\"1234\">\r\n"
3941 "<address uri=\"sip:%s\">\r\n"
3942 "<status status=\"%s\"/>\r\n"
3943 "</address>\r\n"
3944 "</atom>\r\n"
3945 "</presence>\r\n",
3946 sip->username,
3947 sip->username,
3948 sip->username,
3949 sip->status);
3950 return doc;
3955 static gchar* gen_pidf(struct sipe_account_data *sip)
3957 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3958 "<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"
3959 "<tuple id=\"0\">\r\n"
3960 "<status>\r\n"
3961 "<basic>open</basic>\r\n"
3962 "<ep:activities>\r\n"
3963 " <ep:activity>%s</ep:activity>\r\n"
3964 "</ep:activities>"
3965 "</status>\r\n"
3966 "</tuple>\r\n"
3967 "<ci:display-name>%s</ci:display-name>\r\n"
3968 "</presence>",
3969 sip->username,
3970 sip->status,
3971 sip->username);
3972 return doc;
3976 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
3978 int availability = 300; // online
3979 int activity = 400; // Available
3980 gchar *name;
3981 gchar *body;
3982 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
3983 activity = 100;
3984 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
3985 activity = 150;
3986 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
3987 activity = 300;
3988 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
3989 activity = 400;
3990 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
3991 activity = 500;
3992 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
3993 activity = 600;
3994 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
3995 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
3996 availability = 0; // offline
3997 activity = 100;
3998 } else {
3999 activity = 400; // available
4002 name = g_strdup_printf("sip: sip:%s", sip->username);
4003 //@TODO: send user data - state; add hostname in upper case
4004 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4005 send_soap_request_with_cb(sip, body, NULL , NULL);
4006 g_free(name);
4007 g_free(body);
4010 static gboolean
4011 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4013 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4014 if (msg->response == 200) {
4015 sip->status_version = 0;
4016 send_presence_status(sip);
4018 return TRUE;
4021 static gboolean
4022 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4024 if (msg->response == 409) {
4025 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4026 // TODO need to parse the version #'s?
4027 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4028 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4029 gchar *tmp;
4030 gchar *hdr;
4032 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4034 tmp = get_contact(sip);
4035 hdr = g_strdup_printf("Contact: %s\r\n"
4036 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4038 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4040 g_free(tmp);
4041 g_free(hdr);
4042 g_free(uri);
4043 g_free(doc);
4045 return TRUE;
4048 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4050 int code;
4051 gchar *uri;
4052 gchar *doc;
4053 gchar *tmp;
4054 gchar *hdr;
4055 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4056 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4057 code = 12000;
4058 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4059 code = 9000;
4060 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4061 code = 7500;
4062 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4063 code = 6000;
4064 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4065 code = 4500;
4066 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4067 code = 3000;
4068 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4069 code = 0;
4070 } else {
4071 // Offline or invisible
4072 code = 18000;
4075 uri = g_strdup_printf("sip:%s", sip->username);
4076 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4077 sip->status_version, code,
4078 sip->status_version, code,
4079 sip->status_version, note ? note : "",
4080 sip->status_version, note ? note : "",
4081 sip->status_version, note ? note : ""
4083 sip->status_version++;
4085 tmp = get_contact(sip);
4086 hdr = g_strdup_printf("Contact: %s\r\n"
4087 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4089 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4091 g_free(tmp);
4092 g_free(hdr);
4093 g_free(uri);
4094 g_free(doc);
4097 static void send_presence_status(struct sipe_account_data *sip)
4099 PurpleStatus * status = purple_account_get_active_status(sip->account);
4100 const gchar *note;
4101 if (!status) return;
4103 note = purple_status_get_attr_string(status, "message");
4105 if(sip->msrtc_event_categories){
4106 send_presence_category_publish(sip, note);
4107 } else {
4108 send_presence_soap(sip, note);
4112 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4114 gboolean found = FALSE;
4115 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4116 if (msg->response == 0) { /* request */
4117 if (!strcmp(msg->method, "MESSAGE")) {
4118 process_incoming_message(sip, msg);
4119 found = TRUE;
4120 } else if (!strcmp(msg->method, "NOTIFY")) {
4121 purple_debug_info("sipe","send->process_incoming_notify\n");
4122 process_incoming_notify(sip, msg, TRUE, FALSE);
4123 found = TRUE;
4124 } else if (!strcmp(msg->method, "BENOTIFY")) {
4125 purple_debug_info("sipe","send->process_incoming_benotify\n");
4126 process_incoming_notify(sip, msg, TRUE, TRUE);
4127 found = TRUE;
4128 } else if (!strcmp(msg->method, "INVITE")) {
4129 process_incoming_invite(sip, msg);
4130 found = TRUE;
4131 } else if (!strcmp(msg->method, "OPTIONS")) {
4132 process_incoming_options(sip, msg);
4133 found = TRUE;
4134 } else if (!strcmp(msg->method, "INFO")) {
4135 // TODO needs work
4136 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4137 if (from) {
4138 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4140 g_free(from);
4141 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4142 found = TRUE;
4143 } else if (!strcmp(msg->method, "ACK")) {
4144 // ACK's don't need any response
4145 found = TRUE;
4146 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4147 // LCS 2005 sends us these - just respond 200 OK
4148 found = TRUE;
4149 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4150 } else if (!strcmp(msg->method, "BYE")) {
4151 struct sip_im_session *session;
4152 gchar *from;
4153 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4155 from = parse_from(sipmsg_find_header(msg, "From"));
4156 session = find_im_session (sip, from);
4157 g_free(from);
4159 if (session) {
4160 // TODO Let the user know the other user left the conversation?
4161 im_session_destroy(sip, session);
4164 found = TRUE;
4165 } else {
4166 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4168 } else { /* response */
4169 struct transaction *trans = transactions_find(sip, msg);
4170 if (trans) {
4171 if (msg->response == 407) {
4172 gchar *resend, *auth, *ptmp;
4174 if (sip->proxy.retries > 30) return;
4175 sip->proxy.retries++;
4176 /* do proxy authentication */
4178 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4180 fill_auth(sip, ptmp, &sip->proxy);
4181 auth = auth_header(sip, &sip->proxy, trans->msg);
4182 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4183 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4184 g_free(auth);
4185 resend = sipmsg_to_string(trans->msg);
4186 /* resend request */
4187 sendout_pkt(sip->gc, resend);
4188 g_free(resend);
4189 } else {
4190 if (msg->response == 100 || msg->response == 180) {
4191 /* ignore provisional response */
4192 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4193 } else {
4194 sip->proxy.retries = 0;
4195 if (!strcmp(trans->msg->method, "REGISTER")) {
4196 if (msg->response == 401)
4198 sip->registrar.retries++;
4199 sip->registrar.expires = 0;
4201 else
4203 sip->registrar.retries = 0;
4205 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4206 } else {
4207 if (msg->response == 401) {
4208 gchar *resend, *auth, *ptmp;
4210 if (sip->registrar.retries > 4) return;
4211 sip->registrar.retries++;
4213 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4214 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4215 } else {
4216 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4219 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4221 fill_auth(sip, ptmp, &sip->registrar);
4222 auth = auth_header(sip, &sip->registrar, trans->msg);
4223 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4224 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
4226 //sipmsg_remove_header(trans->msg, "Authorization");
4227 //sipmsg_add_header(trans->msg, "Authorization", auth);
4228 g_free(auth);
4229 resend = sipmsg_to_string(trans->msg);
4230 /* resend request */
4231 sendout_pkt(sip->gc, resend);
4232 g_free(resend);
4236 if (trans->callback) {
4237 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4238 /* call the callback to process response*/
4239 (trans->callback)(sip, msg, trans);
4241 /* Not sure if this is needed or what needs to be done
4242 but transactions seem to be removed prematurely so
4243 this only removes them if the response is 200 OK */
4244 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4245 /*Has a bug and it's unneccesary*/
4246 /*transactions_remove(sip, trans);*/
4250 found = TRUE;
4251 } else {
4252 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4255 if (!found) {
4256 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4260 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4262 char *cur;
4263 char *dummy;
4264 struct sipmsg *msg;
4265 int restlen;
4266 cur = conn->inbuf;
4268 /* according to the RFC remove CRLF at the beginning */
4269 while (*cur == '\r' || *cur == '\n') {
4270 cur++;
4272 if (cur != conn->inbuf) {
4273 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4274 conn->inbufused = strlen(conn->inbuf);
4277 /* Received a full Header? */
4278 sip->processing_input = TRUE;
4279 while (sip->processing_input &&
4280 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4281 time_t currtime = time(NULL);
4282 cur += 2;
4283 cur[0] = '\0';
4284 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4285 msg = sipmsg_parse_header(conn->inbuf);
4286 cur[0] = '\r';
4287 cur += 2;
4288 restlen = conn->inbufused - (cur - conn->inbuf);
4289 if (restlen >= msg->bodylen) {
4290 dummy = g_malloc(msg->bodylen + 1);
4291 memcpy(dummy, cur, msg->bodylen);
4292 dummy[msg->bodylen] = '\0';
4293 msg->body = dummy;
4294 cur += msg->bodylen;
4295 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4296 conn->inbufused = strlen(conn->inbuf);
4297 } else {
4298 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4299 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4300 sipmsg_free(msg);
4301 return;
4304 /*if (msg->body) {
4305 purple_debug_info("sipe", "body:\n%s", msg->body);
4308 // Verify the signature before processing it
4309 if (sip->registrar.ntlm_key) {
4310 struct sipmsg_breakdown msgbd;
4311 gchar *signature_input_str;
4312 gchar *signature = NULL;
4313 gchar *rspauth;
4314 msgbd.msg = msg;
4315 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4316 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4317 if (signature_input_str != NULL) {
4318 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4320 g_free(signature_input_str);
4322 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4324 if (signature != NULL) {
4325 if (rspauth != NULL) {
4326 if (purple_ntlm_verify_signature (signature, rspauth)) {
4327 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4328 process_input_message(sip, msg);
4329 } else {
4330 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4331 purple_connection_error(sip->gc, _("Invalid message signature received"));
4332 sip->gc->wants_to_die = TRUE;
4334 } else if (msg->response == 401) {
4335 purple_connection_error(sip->gc, _("Wrong Password"));
4336 sip->gc->wants_to_die = TRUE;
4338 g_free(signature);
4341 g_free(rspauth);
4342 sipmsg_breakdown_free(&msgbd);
4343 } else {
4344 process_input_message(sip, msg);
4347 sipmsg_free(msg);
4351 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4353 PurpleConnection *gc = data;
4354 struct sipe_account_data *sip = gc->proto_data;
4355 struct sipmsg *msg;
4356 int len;
4357 time_t currtime;
4359 static char buffer[65536];
4360 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4361 buffer[len] = '\0';
4362 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4363 msg = sipmsg_parse_msg(buffer);
4364 if (msg) process_input_message(sip, msg);
4368 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4370 struct sipe_account_data *sip = gc->proto_data;
4371 PurpleSslConnection *gsc = sip->gsc;
4373 purple_debug_error("sipe", "%s",debug);
4374 purple_connection_error(gc, msg);
4376 /* Invalidate this connection. Next send will open a new one */
4377 if (gsc) {
4378 connection_remove(sip, gsc->fd);
4379 purple_ssl_close(gsc);
4381 sip->gsc = NULL;
4382 sip->fd = -1;
4385 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4387 PurpleConnection *gc = data;
4388 struct sipe_account_data *sip;
4389 struct sip_connection *conn;
4390 int readlen, len;
4391 gboolean firstread = TRUE;
4393 /* NOTE: This check *IS* necessary */
4394 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4395 purple_ssl_close(gsc);
4396 return;
4399 sip = gc->proto_data;
4400 conn = connection_find(sip, gsc->fd);
4401 if (conn == NULL) {
4402 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4403 gc->wants_to_die = TRUE;
4404 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4405 return;
4408 /* Read all available data from the SSL connection */
4409 do {
4410 /* Increase input buffer size as needed */
4411 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4412 conn->inbuflen += SIMPLE_BUF_INC;
4413 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4414 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4417 /* Try to read as much as there is space left in the buffer */
4418 readlen = conn->inbuflen - conn->inbufused - 1;
4419 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4421 if (len < 0 && errno == EAGAIN) {
4422 /* Try again later */
4423 return;
4424 } else if (len < 0) {
4425 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4426 return;
4427 } else if (firstread && (len == 0)) {
4428 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4429 return;
4432 conn->inbufused += len;
4433 firstread = FALSE;
4435 /* Equivalence indicates that there is possibly more data to read */
4436 } while (len == readlen);
4438 conn->inbuf[conn->inbufused] = '\0';
4439 process_input(sip, conn);
4443 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4445 PurpleConnection *gc = data;
4446 struct sipe_account_data *sip = gc->proto_data;
4447 int len;
4448 struct sip_connection *conn = connection_find(sip, source);
4449 if (!conn) {
4450 purple_debug_error("sipe", "Connection not found!\n");
4451 return;
4454 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4455 conn->inbuflen += SIMPLE_BUF_INC;
4456 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4459 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4461 if (len < 0 && errno == EAGAIN)
4462 return;
4463 else if (len <= 0) {
4464 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4465 connection_remove(sip, source);
4466 if (sip->fd == source) sip->fd = -1;
4467 return;
4470 conn->inbufused += len;
4471 conn->inbuf[conn->inbufused] = '\0';
4473 process_input(sip, conn);
4476 /* Callback for new connections on incoming TCP port */
4477 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4479 PurpleConnection *gc = data;
4480 struct sipe_account_data *sip = gc->proto_data;
4481 struct sip_connection *conn;
4483 int newfd = accept(source, NULL, NULL);
4485 conn = connection_create(sip, newfd);
4487 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4490 static void login_cb(gpointer data, gint source, const gchar *error_message)
4492 PurpleConnection *gc = data;
4493 struct sipe_account_data *sip;
4494 struct sip_connection *conn;
4496 if (!PURPLE_CONNECTION_IS_VALID(gc))
4498 if (source >= 0)
4499 close(source);
4500 return;
4503 if (source < 0) {
4504 purple_connection_error(gc, _("Could not connect"));
4505 return;
4508 sip = gc->proto_data;
4509 sip->fd = source;
4510 sip->last_keepalive = time(NULL);
4512 conn = connection_create(sip, source);
4514 do_register(sip);
4516 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4519 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4521 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4522 if (sip == NULL) return;
4524 do_register(sip);
4527 static guint sipe_ht_hash_nick(const char *nick)
4529 char *lc = g_utf8_strdown(nick, -1);
4530 guint bucket = g_str_hash(lc);
4531 g_free(lc);
4533 return bucket;
4536 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4538 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4541 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4543 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4545 sip->listen_data = NULL;
4547 if (listenfd == -1) {
4548 purple_connection_error(sip->gc, _("Could not create listen socket"));
4549 return;
4552 sip->fd = listenfd;
4554 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4555 sip->listenfd = sip->fd;
4557 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4559 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4560 do_register(sip);
4563 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4565 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4566 int addr_size;
4568 sip->query_data = NULL;
4570 if (!hosts || !hosts->data) {
4571 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4572 return;
4575 addr_size = GPOINTER_TO_INT(hosts->data);
4576 hosts = g_slist_remove(hosts, hosts->data);
4577 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4578 g_free(hosts->data);
4579 hosts = g_slist_remove(hosts, hosts->data);
4580 while (hosts) {
4581 hosts = g_slist_remove(hosts, hosts->data);
4582 g_free(hosts->data);
4583 hosts = g_slist_remove(hosts, hosts->data);
4586 /* create socket for incoming connections */
4587 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4588 sipe_udp_host_resolved_listen_cb, sip);
4589 if (sip->listen_data == NULL) {
4590 purple_connection_error(sip->gc, _("Could not create listen socket"));
4591 return;
4595 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4596 gpointer data)
4598 PurpleConnection *gc = data;
4599 struct sipe_account_data *sip;
4601 /* If the connection is already disconnected, we don't need to do anything else */
4602 if (!PURPLE_CONNECTION_IS_VALID(gc))
4603 return;
4605 sip = gc->proto_data;
4606 sip->fd = -1;
4607 sip->gsc = NULL;
4609 switch(error) {
4610 case PURPLE_SSL_CONNECT_FAILED:
4611 purple_connection_error(gc, _("Connection Failed"));
4612 break;
4613 case PURPLE_SSL_HANDSHAKE_FAILED:
4614 purple_connection_error(gc, _("SSL Handshake Failed"));
4615 break;
4616 case PURPLE_SSL_CERTIFICATE_INVALID:
4617 purple_connection_error(gc, _("SSL Certificate Invalid"));
4618 break;
4622 static void
4623 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4625 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4626 PurpleProxyConnectData *connect_data;
4628 sip->listen_data = NULL;
4630 sip->listenfd = listenfd;
4631 if (sip->listenfd == -1) {
4632 purple_connection_error(sip->gc, _("Could not create listen socket"));
4633 return;
4636 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4637 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4638 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4639 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4640 sipe_newconn_cb, sip->gc);
4641 purple_debug_info("sipe", "connecting to %s port %d\n",
4642 sip->realhostname, sip->realport);
4643 /* open tcp connection to the server */
4644 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4645 sip->realport, login_cb, sip->gc);
4647 if (connect_data == NULL) {
4648 purple_connection_error(sip->gc, _("Couldn't create socket"));
4653 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4655 PurpleAccount *account = sip->account;
4656 PurpleConnection *gc = sip->gc;
4658 if (purple_account_get_bool(account, "useport", FALSE)) {
4659 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4660 port = purple_account_get_int(account, "port", 0);
4661 } else {
4662 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4665 sip->realhostname = hostname;
4666 sip->realport = port;
4668 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4669 hostname, port);
4671 /* TODO: is there a good default grow size? */
4672 if (sip->transport != SIPE_TRANSPORT_UDP)
4673 sip->txbuf = purple_circ_buffer_new(0);
4675 if (sip->transport == SIPE_TRANSPORT_TLS) {
4676 /* SSL case */
4677 if (!purple_ssl_is_supported()) {
4678 gc->wants_to_die = TRUE;
4679 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4680 return;
4683 purple_debug_info("sipe", "using SSL\n");
4685 sip->gsc = purple_ssl_connect(account, hostname, port,
4686 login_cb_ssl, sipe_ssl_connect_failure, gc);
4687 if (sip->gsc == NULL) {
4688 purple_connection_error(gc, _("Could not create SSL context"));
4689 return;
4691 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4692 /* UDP case */
4693 purple_debug_info("sipe", "using UDP\n");
4695 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4696 if (sip->query_data == NULL) {
4697 purple_connection_error(gc, _("Could not resolve hostname"));
4699 } else {
4700 /* TCP case */
4701 purple_debug_info("sipe", "using TCP\n");
4702 /* create socket for incoming connections */
4703 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4704 sipe_tcp_connect_listen_cb, sip);
4705 if (sip->listen_data == NULL) {
4706 purple_connection_error(gc, _("Could not create listen socket"));
4707 return;
4712 /* Service list for autodection */
4713 static const struct sipe_service_data service_autodetect[] = {
4714 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4715 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4716 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4717 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4718 { NULL, NULL, 0 }
4721 /* Service list for SSL/TLS */
4722 static const struct sipe_service_data service_tls[] = {
4723 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4724 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4725 { NULL, NULL, 0 }
4728 /* Service list for TCP */
4729 static const struct sipe_service_data service_tcp[] = {
4730 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4731 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4732 { NULL, NULL, 0 }
4735 /* Service list for UDP */
4736 static const struct sipe_service_data service_udp[] = {
4737 { "sip", "udp", SIPE_TRANSPORT_UDP },
4738 { NULL, NULL, 0 }
4741 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4742 static void resolve_next_service(struct sipe_account_data *sip,
4743 const struct sipe_service_data *start)
4745 if (start) {
4746 sip->service_data = start;
4747 } else {
4748 sip->service_data++;
4749 if (sip->service_data->service == NULL) {
4750 gchar *hostname;
4751 /* Try connecting to the SIP hostname directly */
4752 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4753 if (sip->auto_transport) {
4754 // If SSL is supported, default to using it; OCS servers aren't configured
4755 // by default to accept TCP
4756 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4757 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4758 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4761 hostname = g_strdup(sip->sipdomain);
4762 create_connection(sip, hostname, 0);
4763 return;
4767 /* Try to resolve next service */
4768 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4769 sip->service_data->transport,
4770 sip->sipdomain,
4771 srvresolved, sip);
4774 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4776 struct sipe_account_data *sip = data;
4778 sip->srv_query_data = NULL;
4780 /* find the host to connect to */
4781 if (results) {
4782 gchar *hostname = g_strdup(resp->hostname);
4783 int port = resp->port;
4784 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4785 hostname, port);
4786 g_free(resp);
4788 sip->transport = sip->service_data->type;
4790 create_connection(sip, hostname, port);
4791 } else {
4792 resolve_next_service(sip, NULL);
4796 static void sipe_login(PurpleAccount *account)
4798 PurpleConnection *gc;
4799 struct sipe_account_data *sip;
4800 gchar **signinname_login, **userserver, **domain_user;
4801 const char *transport;
4803 const char *username = purple_account_get_username(account);
4804 gc = purple_account_get_connection(account);
4806 if (strpbrk(username, "\t\v\r\n") != NULL) {
4807 gc->wants_to_die = TRUE;
4808 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
4809 return;
4812 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4813 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4814 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4815 sip->gc = gc;
4816 sip->account = account;
4817 sip->reregister_set = FALSE;
4818 sip->reauthenticate_set = FALSE;
4819 sip->subscribed = FALSE;
4820 sip->subscribed_buddies = FALSE;
4822 signinname_login = g_strsplit(username, ",", 2);
4824 userserver = g_strsplit(signinname_login[0], "@", 2);
4825 purple_connection_set_display_name(gc, userserver[0]);
4826 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4827 sip->sipdomain = g_strdup(userserver[1]);
4829 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
4830 gc->wants_to_die = TRUE;
4831 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4832 return;
4835 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4836 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4837 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4839 sip->password = g_strdup(purple_connection_get_password(gc));
4841 g_strfreev(userserver);
4842 g_strfreev(domain_user);
4843 g_strfreev(signinname_login);
4845 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4847 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4849 /* TODO: Set the status correctly. */
4850 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4852 transport = purple_account_get_string(account, "transport", "auto");
4853 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4854 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4855 SIPE_TRANSPORT_UDP;
4857 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4858 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4859 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4860 } else if (strcmp(transport, "auto") == 0) {
4861 sip->auto_transport = TRUE;
4862 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4863 } else if (strcmp(transport, "tls") == 0) {
4864 resolve_next_service(sip, service_tls);
4865 } else if (strcmp(transport, "tcp") == 0) {
4866 resolve_next_service(sip, service_tcp);
4867 } else {
4868 resolve_next_service(sip, service_udp);
4872 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4874 connection_free_all(sip);
4876 g_free(sip->epid);
4877 sip->epid = NULL;
4879 if (sip->query_data != NULL)
4880 purple_dnsquery_destroy(sip->query_data);
4881 sip->query_data = NULL;
4883 if (sip->srv_query_data != NULL)
4884 purple_srv_cancel(sip->srv_query_data);
4885 sip->srv_query_data = NULL;
4887 if (sip->listen_data != NULL)
4888 purple_network_listen_cancel(sip->listen_data);
4889 sip->listen_data = NULL;
4891 if (sip->gsc != NULL)
4892 purple_ssl_close(sip->gsc);
4893 sip->gsc = NULL;
4895 sipe_auth_free(&sip->registrar);
4896 sipe_auth_free(&sip->proxy);
4898 if (sip->txbuf)
4899 purple_circ_buffer_destroy(sip->txbuf);
4900 sip->txbuf = NULL;
4902 g_free(sip->realhostname);
4903 sip->realhostname = NULL;
4905 if (sip->listenpa)
4906 purple_input_remove(sip->listenpa);
4907 sip->listenpa = 0;
4908 if (sip->tx_handler)
4909 purple_input_remove(sip->tx_handler);
4910 sip->tx_handler = 0;
4911 if (sip->resendtimeout)
4912 purple_timeout_remove(sip->resendtimeout);
4913 sip->resendtimeout = 0;
4914 if (sip->timeouts) {
4915 GSList *entry = sip->timeouts;
4916 while (entry) {
4917 struct scheduled_action *sched_action = entry->data;
4918 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4919 purple_timeout_remove(sched_action->timeout_handler);
4920 g_free(sched_action->payload);
4921 g_free(sched_action->name);
4922 g_free(sched_action);
4923 entry = entry->next;
4926 g_slist_free(sip->timeouts);
4928 if (sip->allow_events) {
4929 GSList *entry = sip->allow_events;
4930 while (entry) {
4931 g_free(entry->data);
4932 entry = entry->next;
4935 g_slist_free(sip->allow_events);
4937 if (sip->contact)
4938 g_free(sip->contact);
4939 sip->contact = NULL;
4940 if (sip->regcallid)
4941 g_free(sip->regcallid);
4942 sip->regcallid = NULL;
4944 sip->fd = -1;
4945 sip->processing_input = FALSE;
4949 * A callback for g_hash_table_foreach_remove
4951 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4953 sipe_free_buddy(buddy);
4956 static void sipe_close(PurpleConnection *gc)
4958 struct sipe_account_data *sip = gc->proto_data;
4960 if (sip) {
4961 /* leave all conversations */
4962 im_session_close_all(sip);
4964 /* unregister */
4965 do_register_exp(sip, 0);
4967 sipe_connection_cleanup(sip);
4968 g_free(sip->sipdomain);
4969 g_free(sip->username);
4970 g_free(sip->password);
4971 g_free(sip->authdomain);
4972 g_free(sip->authuser);
4973 g_free(sip->status);
4975 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4976 g_hash_table_destroy(sip->buddies);
4978 g_free(gc->proto_data);
4979 gc->proto_data = NULL;
4982 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4984 PurpleAccount *acct = purple_connection_get_account(gc);
4985 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4986 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4987 if (conv == NULL)
4988 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4989 purple_conversation_present(conv);
4990 g_free(id);
4993 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4996 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4997 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
5000 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5002 PurpleNotifySearchResults *results;
5003 PurpleNotifySearchColumn *column;
5004 xmlnode *searchResults;
5005 xmlnode *mrow;
5006 int match_count = 0;
5007 gboolean more = FALSE;
5008 gchar *secondary;
5010 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5012 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5013 if (!searchResults) {
5014 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5015 return FALSE;
5018 results = purple_notify_searchresults_new();
5020 if (results == NULL) {
5021 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5022 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5024 xmlnode_free(searchResults);
5025 return FALSE;
5028 column = purple_notify_searchresults_column_new(_("User Name"));
5029 purple_notify_searchresults_column_add(results, column);
5031 column = purple_notify_searchresults_column_new(_("Name"));
5032 purple_notify_searchresults_column_add(results, column);
5034 column = purple_notify_searchresults_column_new(_("Company"));
5035 purple_notify_searchresults_column_add(results, column);
5037 column = purple_notify_searchresults_column_new(_("Country"));
5038 purple_notify_searchresults_column_add(results, column);
5040 column = purple_notify_searchresults_column_new(_("Email"));
5041 purple_notify_searchresults_column_add(results, column);
5043 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5044 GList *row = NULL;
5046 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5047 row = g_list_append(row, g_strdup(uri_parts[1]));
5048 g_strfreev(uri_parts);
5050 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5051 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5052 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5053 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5055 purple_notify_searchresults_row_add(results, row);
5056 match_count++;
5059 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5060 char *data = xmlnode_get_data_unescaped(mrow);
5061 more = (g_strcasecmp(data, "true") == 0);
5062 g_free(data);
5065 secondary = g_strdup_printf(
5066 dngettext(GETTEXT_PACKAGE,
5067 "Found %d contact%s:",
5068 "Found %d contacts%s:", match_count),
5069 match_count, more ? _(" (more matched your query)") : "");
5071 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5072 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5073 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5075 g_free(secondary);
5076 xmlnode_free(searchResults);
5077 return TRUE;
5080 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5082 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5083 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5084 unsigned i = 0;
5086 do {
5087 PurpleRequestField *field = entries->data;
5088 const char *id = purple_request_field_get_id(field);
5089 const char *value = purple_request_field_string_get_value(field);
5091 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5093 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5094 } while ((entries = g_list_next(entries)) != NULL);
5095 attrs[i] = NULL;
5097 if (i > 0) {
5098 gchar *query = g_strjoinv(NULL, attrs);
5099 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5100 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5101 send_soap_request_with_cb(gc->proto_data, body,
5102 (TransCallback) process_search_contact_response, NULL);
5103 g_free(body);
5104 g_free(query);
5107 g_strfreev(attrs);
5110 static void sipe_show_find_contact(PurplePluginAction *action)
5112 PurpleConnection *gc = (PurpleConnection *) action->context;
5113 PurpleRequestFields *fields;
5114 PurpleRequestFieldGroup *group;
5115 PurpleRequestField *field;
5117 fields = purple_request_fields_new();
5118 group = purple_request_field_group_new(NULL);
5119 purple_request_fields_add_group(fields, group);
5121 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5122 purple_request_field_group_add_field(group, field);
5123 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5124 purple_request_field_group_add_field(group, field);
5125 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5126 purple_request_field_group_add_field(group, field);
5127 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5128 purple_request_field_group_add_field(group, field);
5130 purple_request_fields(gc,
5131 _("Search"),
5132 _("Search for a Contact"),
5133 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5134 fields,
5135 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5136 _("_Cancel"), NULL,
5137 purple_connection_get_account(gc), NULL, NULL, gc);
5140 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5142 GList *menu = NULL;
5143 PurplePluginAction *act;
5145 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5146 menu = g_list_prepend(menu, act);
5148 menu = g_list_reverse(menu);
5150 return menu;
5153 static void dummy_permit_deny(PurpleConnection *gc)
5157 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5159 return TRUE;
5163 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5165 return TRUE;
5169 static char *sipe_status_text(PurpleBuddy *buddy)
5171 struct sipe_account_data *sip;
5172 struct sipe_buddy *sbuddy;
5173 char *text = NULL;
5175 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5176 if (sip) //happens on pidgin exit
5178 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5179 if (sbuddy && sbuddy->annotation)
5181 text = g_strdup(sbuddy->annotation);
5185 return text;
5188 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5190 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5191 const PurpleStatus *status = purple_presence_get_active_status(presence);
5192 struct sipe_account_data *sip;
5193 struct sipe_buddy *sbuddy;
5194 char *annotation = NULL;
5196 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5197 if (sip) //happens on pidgin exit
5199 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5200 if (sbuddy)
5202 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5206 //Layout
5207 if (purple_presence_is_online(presence))
5209 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5212 if (annotation)
5214 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5215 g_free(annotation);
5220 static GHashTable *
5221 sipe_get_account_text_table(PurpleAccount *account)
5223 GHashTable *table;
5224 table = g_hash_table_new(g_str_hash, g_str_equal);
5225 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5226 return table;
5229 static PurpleBuddy *
5230 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5232 PurpleBuddy *clone;
5233 const gchar *server_alias, *email;
5234 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5236 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5238 purple_blist_add_buddy(clone, NULL, group, NULL);
5240 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5241 if (server_alias) {
5242 purple_blist_server_alias_buddy(clone, server_alias);
5245 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5246 if (email) {
5247 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5250 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5251 //for UI to update;
5252 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5253 return clone;
5256 static void
5257 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5259 PurpleBuddy *buddy, *b;
5260 PurpleConnection *gc;
5261 PurpleGroup * group = purple_find_group(group_name);
5263 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5265 buddy = (PurpleBuddy *)node;
5267 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5268 gc = purple_account_get_connection(buddy->account);
5270 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5271 if (!b){
5272 b = purple_blist_add_buddy_clone(group, buddy);
5275 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5278 static void
5279 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5281 const gchar *email;
5282 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5284 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5285 if (email)
5287 char *mailto = g_strdup_printf("mailto:%s", email);
5288 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5289 #ifndef _WIN32
5291 pid_t pid;
5292 char *const parmList[] = {mailto, NULL};
5293 if ((pid = fork()) == -1)
5295 purple_debug_info("sipe", "fork() error\n");
5297 else if (pid == 0)
5299 execvp("xdg-email", parmList);
5300 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5303 #else
5305 BOOL ret;
5306 _flushall();
5307 errno = 0;
5308 //@TODO resolve env variable %WINDIR% first
5309 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5310 if (errno)
5312 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5315 #endif
5317 g_free(mailto);
5319 else
5321 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5326 * A menu which appear when right-clicking on buddy in contact list.
5328 static GList *
5329 sipe_buddy_menu(PurpleBuddy *buddy)
5331 PurpleBlistNode *g_node;
5332 PurpleGroup *group, *gr_parent;
5333 PurpleMenuAction *act;
5334 GList *menu = NULL;
5335 GList *menu_groups = NULL;
5337 act = purple_menu_action_new(_("Send Email..."),
5338 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5339 NULL, NULL);
5340 menu = g_list_prepend(menu, act);
5342 gr_parent = purple_buddy_get_group(buddy);
5343 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5344 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5345 continue;
5347 group = (PurpleGroup *)g_node;
5348 if (group == gr_parent)
5349 continue;
5351 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5352 continue;
5354 act = purple_menu_action_new(purple_group_get_name(group),
5355 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5356 group->name, NULL);
5357 menu_groups = g_list_prepend(menu_groups, act);
5359 menu_groups = g_list_reverse(menu_groups);
5361 act = purple_menu_action_new(_("Copy to"),
5362 NULL,
5363 NULL, menu_groups);
5364 menu = g_list_prepend(menu, act);
5365 menu = g_list_reverse(menu);
5367 return menu;
5370 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5371 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5372 return sipe_buddy_menu((PurpleBuddy *) node);
5373 } else {
5374 return NULL;
5378 static gboolean
5379 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5381 gboolean ret = TRUE;
5382 char *username = (char *)trans->payload;
5384 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5385 PurpleBuddy *pbuddy;
5386 struct sipe_buddy *sbuddy;
5387 const char *alias;
5388 char *server_alias = NULL;
5389 char *email = NULL;
5390 const char *device_name = NULL;
5392 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5394 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5395 alias = purple_buddy_get_local_alias(pbuddy);
5397 if (sip)
5399 //will query buddy UA's capabilities and send answer to log
5400 sipe_options_request(sip, username);
5402 sbuddy = g_hash_table_lookup(sip->buddies, username);
5403 if (sbuddy)
5405 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5409 if (msg->response != 200) {
5410 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5411 } else {
5412 xmlnode *searchResults;
5413 xmlnode *mrow;
5415 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5416 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5417 if (!searchResults) {
5418 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5419 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5420 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5421 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5422 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5423 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5424 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5425 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5426 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5427 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5428 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5429 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5430 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5431 if (!email || strcmp("", email)) {
5432 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5433 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5437 xmlnode_free(searchResults);
5440 purple_notify_user_info_add_section_break(info);
5442 if (!server_alias || !strcmp("", server_alias)) {
5443 g_free(server_alias);
5444 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5445 if (server_alias) {
5446 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5450 // same as server alias, do not present
5451 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5452 if (alias)
5454 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5457 if (!email || !strcmp("", email)) {
5458 g_free(email);
5459 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5460 if (email) {
5461 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5465 if (device_name)
5467 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5470 /* show a buddy's user info in a nice dialog box */
5471 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5472 username, /* buddy's username */
5473 info, /* body */
5474 NULL, /* callback called when dialog closed */
5475 NULL); /* userdata for callback */
5477 return ret;
5481 * AD search first, LDAP based
5483 static void sipe_get_info(PurpleConnection *gc, const char *username)
5485 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5486 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5488 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5489 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5490 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5491 g_free(body);
5492 g_free(row);
5495 static PurplePlugin *my_protocol = NULL;
5497 static PurplePluginProtocolInfo prpl_info =
5500 NULL, /* user_splits */
5501 NULL, /* protocol_options */
5502 NO_BUDDY_ICONS, /* icon_spec */
5503 sipe_list_icon, /* list_icon */
5504 NULL, /* list_emblems */
5505 sipe_status_text, /* status_text */
5506 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5507 sipe_status_types, /* away_states */
5508 sipe_blist_node_menu, /* blist_node_menu */
5509 NULL, /* chat_info */
5510 NULL, /* chat_info_defaults */
5511 sipe_login, /* login */
5512 sipe_close, /* close */
5513 sipe_im_send, /* send_im */
5514 NULL, /* set_info */ // TODO maybe
5515 sipe_send_typing, /* send_typing */
5516 sipe_get_info, /* get_info */
5517 sipe_set_status, /* set_status */
5518 NULL, /* set_idle */
5519 NULL, /* change_passwd */
5520 sipe_add_buddy, /* add_buddy */
5521 NULL, /* add_buddies */
5522 sipe_remove_buddy, /* remove_buddy */
5523 NULL, /* remove_buddies */
5524 sipe_add_permit, /* add_permit */
5525 sipe_add_deny, /* add_deny */
5526 sipe_add_deny, /* rem_permit */
5527 sipe_add_permit, /* rem_deny */
5528 dummy_permit_deny, /* set_permit_deny */
5529 NULL, /* join_chat */
5530 NULL, /* reject_chat */
5531 NULL, /* get_chat_name */
5532 NULL, /* chat_invite */
5533 NULL, /* chat_leave */
5534 NULL, /* chat_whisper */
5535 NULL, /* chat_send */
5536 sipe_keep_alive, /* keepalive */
5537 NULL, /* register_user */
5538 NULL, /* get_cb_info */ // deprecated
5539 NULL, /* get_cb_away */ // deprecated
5540 sipe_alias_buddy, /* alias_buddy */
5541 sipe_group_buddy, /* group_buddy */
5542 sipe_rename_group, /* rename_group */
5543 NULL, /* buddy_free */
5544 sipe_convo_closed, /* convo_closed */
5545 purple_normalize_nocase, /* normalize */
5546 NULL, /* set_buddy_icon */
5547 sipe_remove_group, /* remove_group */
5548 NULL, /* get_cb_real_name */ // TODO?
5549 NULL, /* set_chat_topic */
5550 NULL, /* find_blist_chat */
5551 NULL, /* roomlist_get_list */
5552 NULL, /* roomlist_cancel */
5553 NULL, /* roomlist_expand_category */
5554 NULL, /* can_receive_file */
5555 NULL, /* send_file */
5556 NULL, /* new_xfer */
5557 NULL, /* offline_message */
5558 NULL, /* whiteboard_prpl_ops */
5559 sipe_send_raw, /* send_raw */
5560 NULL, /* roomlist_room_serialize */
5561 NULL, /* unregister_user */
5562 NULL, /* send_attention */
5563 NULL, /* get_attention_types */
5565 sizeof(PurplePluginProtocolInfo), /* struct_size */
5566 sipe_get_account_text_table, /* get_account_text_table */
5570 static PurplePluginInfo info = {
5571 PURPLE_PLUGIN_MAGIC,
5572 PURPLE_MAJOR_VERSION,
5573 PURPLE_MINOR_VERSION,
5574 PURPLE_PLUGIN_PROTOCOL, /**< type */
5575 NULL, /**< ui_requirement */
5576 0, /**< flags */
5577 NULL, /**< dependencies */
5578 PURPLE_PRIORITY_DEFAULT, /**< priority */
5579 "prpl-sipe", /**< id */
5580 "Microsoft LCS/OCS", /**< name */
5581 VERSION, /**< version */
5582 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5583 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5584 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5585 "Gabriel Burt <gburt@novell.com>", /**< author */
5586 PURPLE_WEBSITE, /**< homepage */
5587 sipe_plugin_load, /**< load */
5588 sipe_plugin_unload, /**< unload */
5589 sipe_plugin_destroy, /**< destroy */
5590 NULL, /**< ui_info */
5591 &prpl_info, /**< extra_info */
5592 NULL,
5593 sipe_actions,
5594 NULL,
5595 NULL,
5596 NULL,
5597 NULL
5600 static void sipe_plugin_destroy(PurplePlugin *plugin)
5602 GList *entry;
5604 entry = prpl_info.protocol_options;
5605 while (entry) {
5606 purple_account_option_destroy(entry->data);
5607 entry = g_list_delete_link(entry, entry);
5609 prpl_info.protocol_options = NULL;
5611 entry = prpl_info.user_splits;
5612 while (entry) {
5613 purple_account_user_split_destroy(entry->data);
5614 entry = g_list_delete_link(entry, entry);
5616 prpl_info.user_splits = NULL;
5619 static void init_plugin(PurplePlugin *plugin)
5621 PurpleAccountUserSplit *split;
5622 PurpleAccountOption *option;
5624 #ifdef ENABLE_NLS
5625 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5626 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5627 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5628 #endif
5630 purple_plugin_register(plugin);
5632 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5633 purple_account_user_split_set_reverse(split, FALSE);
5634 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5636 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5637 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5638 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5639 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5641 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5642 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5643 // Translators: noun (networking port)
5644 option = purple_account_option_int_new(_("Port"), "port", 5061);
5645 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5647 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5648 purple_account_option_add_list_item(option, _("Auto"), "auto");
5649 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5650 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5651 purple_account_option_add_list_item(option, _("UDP"), "udp");
5652 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5654 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5655 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5657 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5658 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5660 // TODO commented out so won't show in the preferences until we fix krb message signing
5661 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5662 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5664 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5665 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5666 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5669 my_protocol = plugin;
5672 /* I had to redefined the function for it load, but works */
5673 gboolean purple_init_plugin(PurplePlugin *plugin){
5674 plugin->info = &(info);
5675 init_plugin((plugin));
5676 sipe_plugin_load((plugin));
5677 return purple_plugin_register(plugin);
5681 Local Variables:
5682 mode: c
5683 c-file-style: "bsd"
5684 indent-tabs-mode: t
5685 tab-width: 8
5686 End: