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