Fixed the resubscription process when expires comes to 0.
[siplcs.git] / src / sipe.c
blob8bf5ac1ccad1722d4aa8fcc1260eff2324a746fb
1 /**
2 * @file sipe.c
4 * pidgin-sipe
5 *
6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
11 * ***
12 * Thanks to Google's Summer of Code Program and the helpful mentors
13 * ***
15 * Session-based SIP MESSAGE documentation:
16 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #ifndef _WIN32
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <netinet/in.h>
38 #include <net/if.h>
39 #ifdef ENABLE_NLS
40 # include <libintl.h>
41 # define _(String) ((const char *) gettext (String))
42 #else
43 # define _(String) ((const char *) (String))
44 #endif /* ENABLE_NLS */
45 #else
46 #ifdef _DLL
47 #define _WS2TCPIP_H_
48 #define _WINSOCK2API_
49 #define _LIBC_INTERNAL_
50 #endif /* _DLL */
52 #include "internal.h"
53 #endif /* _WIN32 */
55 #include <time.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <string.h>
59 #include <glib.h>
62 #include "accountopt.h"
63 #include "blist.h"
64 #include "conversation.h"
65 #include "dnsquery.h"
66 #include "debug.h"
67 #include "notify.h"
68 #include "privacy.h"
69 #include "prpl.h"
70 #include "plugin.h"
71 #include "util.h"
72 #include "version.h"
73 #include "network.h"
74 #include "xmlnode.h"
75 #include "mime.h"
77 #include "sipe.h"
78 #include "sip-ntlm.h"
79 #ifdef USE_KERBEROS
80 #include "sipkrb5.h"
81 #endif /*USE_KERBEROS*/
83 #include "sipmsg.h"
84 #include "sipe-sign.h"
85 #include "dnssrv.h"
86 #include "request.h"
88 /* Keep in sync with sipe_transport_type! */
89 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
90 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
92 /* Status identifiers (see also: sipe_status_types()) */
93 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
94 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
95 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
96 /* PURPLE_STATUS_UNAVAILABLE: */
97 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
98 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
99 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
100 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
101 /* PURPLE_STATUS_AWAY: */
102 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
103 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
104 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
105 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
106 /* ??? PURPLE_STATUS_MOBILE */
107 /* ??? PURPLE_STATUS_TUNE */
109 static char *gentag()
111 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
114 static gchar *get_epid(struct sipe_account_data *sip)
116 if (!sip->epid) {
117 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
119 return g_strdup(sip->epid);
122 static char *genbranch()
124 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
125 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
126 rand() & 0xFFFF, rand() & 0xFFFF);
129 static char *gencallid()
131 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
132 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
133 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
134 rand() & 0xFFFF, rand() & 0xFFFF);
137 static gchar *find_tag(const gchar *hdr)
139 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
140 if (!tag) {
141 // In case it's at the end and there's no trailing ;
142 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
144 return tag;
148 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
150 return "sipe";
153 static void sipe_plugin_destroy(PurplePlugin *plugin);
155 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
157 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
158 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
159 gpointer data);
161 static void sipe_close(PurpleConnection *gc);
163 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name);
164 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip);
165 static void send_presence_status(struct sipe_account_data *sip);
167 static void sendout_pkt(PurpleConnection *gc, const char *buf);
169 static void sipe_keep_alive_timeout(struct sipe_account_data *sip, gchar *timeout)
171 if (timeout != NULL) {
172 sscanf(timeout, "%u", &sip->keepalive_timeout);
173 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
174 sip->keepalive_timeout);
176 g_free(timeout);
179 static void sipe_keep_alive(PurpleConnection *gc)
181 struct sipe_account_data *sip = gc->proto_data;
182 if (sip->transport == SIPE_TRANSPORT_UDP) {
183 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
184 gchar buf[2] = {0, 0};
185 purple_debug_info("sipe", "sending keep alive\n");
186 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
187 } else {
188 time_t now = time(NULL);
189 if ((sip->keepalive_timeout > 0) &&
190 ((now - sip->last_keepalive) >= sip->keepalive_timeout)
191 #if PURPLE_VERSION_CHECK(2,4,0)
192 && ((now - gc->last_received) >= sip->keepalive_timeout)
193 #endif
195 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
196 sendout_pkt(gc, "\r\n\r\n");
197 sip->last_keepalive = now;
202 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
204 struct sip_connection *ret = NULL;
205 GSList *entry = sip->openconns;
206 while (entry) {
207 ret = entry->data;
208 if (ret->fd == fd) return ret;
209 entry = entry->next;
211 return NULL;
214 static void sipe_auth_free(struct sip_auth *auth)
216 g_free(auth->nonce);
217 auth->nonce = NULL;
218 g_free(auth->opaque);
219 auth->opaque = NULL;
220 g_free(auth->realm);
221 auth->realm = NULL;
222 g_free(auth->target);
223 auth->target = NULL;
224 g_free(auth->digest_session_key);
225 auth->digest_session_key = NULL;
226 g_free(auth->ntlm_key);
227 auth->ntlm_key = NULL;
228 auth->type = AUTH_TYPE_UNSET;
229 auth->retries = 0;
230 auth->expires = 0;
233 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
235 struct sip_connection *ret = g_new0(struct sip_connection, 1);
236 ret->fd = fd;
237 sip->openconns = g_slist_append(sip->openconns, ret);
238 return ret;
241 static void connection_remove(struct sipe_account_data *sip, int fd)
243 struct sip_connection *conn = connection_find(sip, fd);
244 if (conn) {
245 sip->openconns = g_slist_remove(sip->openconns, conn);
246 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
247 g_free(conn->inbuf);
248 g_free(conn);
252 static void connection_free_all(struct sipe_account_data *sip)
254 struct sip_connection *ret = NULL;
255 GSList *entry = sip->openconns;
256 while (entry) {
257 ret = entry->data;
258 connection_remove(sip, ret->fd);
259 entry = sip->openconns;
263 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
265 const gchar *method = msg->method;
266 const gchar *target = msg->target;
267 gchar noncecount[9];
268 gchar *response;
269 gchar *ret;
270 gchar *tmp = NULL;
271 const char *authdomain = sip->authdomain;
272 const char *authuser = sip->authuser;
273 //const char *krb5_realm;
274 const char *host;
275 //gchar *krb5_token = NULL;
277 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
278 // and do error checking
280 // KRB realm should always be uppercase
281 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
283 if (sip->realhostname) {
284 host = sip->realhostname;
285 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
286 host = purple_account_get_string(sip->account, "proxy", "");
287 } else {
288 host = sip->sipdomain;
291 /*gboolean new_auth = krb5_auth.gss_context == NULL;
292 if (new_auth) {
293 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
296 if (new_auth || force_reauth) {
297 krb5_token = krb5_auth.base64_token;
300 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
301 krb5_token = krb5_auth.base64_token;*/
303 if (!authdomain) {
304 authdomain = "";
307 if (!authuser || strlen(authuser) < 1) {
308 authuser = sip->username;
311 if (auth->type == AUTH_TYPE_DIGEST) { /* Digest */
312 sprintf(noncecount, "%08d", auth->nc++);
313 response = purple_cipher_http_digest_calculate_response(
314 "md5", method, target, NULL, NULL,
315 auth->nonce, noncecount, NULL, auth->digest_session_key);
316 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
318 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);
319 g_free(response);
320 return ret;
321 } else if (auth->type == AUTH_TYPE_NTLM) { /* NTLM */
322 // If we have a signature for the message, include that
323 if (msg->signature) {
324 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);
325 return tmp;
328 if (auth->nc == 3 && auth->nonce && auth->ntlm_key == NULL) {
329 const gchar *ntlm_key;
330 gchar *gssapi_data;
331 #if GLIB_CHECK_VERSION(2,8,0)
332 const gchar * hostname = g_get_host_name();
333 #else
334 static char hostname[256];
335 int ret = gethostname(hostname, sizeof(hostname));
336 hostname[sizeof(hostname) - 1] = '\0';
337 if (ret == -1 || hostname[0] == '\0') {
338 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Error when getting host name. Using \"localhost.\"\n");
339 g_strerror(errno);
340 strcpy(hostname, "localhost");
342 #endif
343 /*const gchar * hostname = purple_get_host_name();*/
345 gssapi_data = purple_ntlm_gen_authenticate(&ntlm_key, authuser, sip->password, hostname, authdomain, (const guint8 *)auth->nonce, &auth->flags);
346 auth->ntlm_key = (gchar *)ntlm_key;
347 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, gssapi_data);
348 g_free(gssapi_data);
349 return tmp;
352 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
353 return tmp;
354 } else if (auth->type == AUTH_TYPE_KERBEROS) {
355 /* Kerberos */
356 if (auth->nc == 3) {
357 /*if (new_auth || force_reauth) {
358 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
359 if (auth->opaque) {
360 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);
361 } else {
362 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
364 } else {
365 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
366 gchar * mic = "MICTODO";
367 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
368 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
369 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
370 //auth->opaque ? auth->opaque : "", auth->target);
371 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
372 //g_free(mic);
374 return tmp;
376 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth->target);
379 sprintf(noncecount, "%08d", auth->nc++);
380 response = purple_cipher_http_digest_calculate_response(
381 "md5", method, target, NULL, NULL,
382 auth->nonce, noncecount, NULL, auth->digest_session_key);
383 purple_debug(PURPLE_DEBUG_MISC, "sipe", "response %s\n", response);
385 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);
386 g_free(response);
387 return ret;
390 static char *parse_attribute(const char *attrname, const char *source)
392 const char *tmp, *tmp2;
393 char *retval = NULL;
394 int len = strlen(attrname);
396 if (!strncmp(source, attrname, len)) {
397 tmp = source + len;
398 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
399 if (tmp2)
400 retval = g_strndup(tmp, tmp2 - tmp);
401 else
402 retval = g_strdup(tmp);
405 return retval;
408 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
410 int i = 0;
411 const char *authuser;
412 char *tmp;
413 gchar **parts;
414 //const char *krb5_realm;
415 //const char *host;
417 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
418 // and do error checking
420 // KRB realm should always be uppercase
421 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
423 if (sip->realhostname) {
424 host = sip->realhostname;
425 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
426 host = purple_account_get_string(sip->account, "proxy", "");
427 } else {
428 host = sip->sipdomain;
431 authuser = sip->authuser;
433 if (!authuser || strlen(authuser) < 1) {
434 authuser = sip->username;
437 if (!hdr) {
438 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
439 return;
442 if (!g_strncasecmp(hdr, "NTLM", 4)) {
443 auth->type = AUTH_TYPE_NTLM;
444 parts = g_strsplit(hdr+5, "\", ", 0);
445 i = 0;
446 while (parts[i]) {
447 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
448 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
449 g_free(auth->nonce);
450 auth->nonce = g_memdup(purple_ntlm_parse_challenge(tmp, &auth->flags), 8);
451 g_free(tmp);
453 if ((tmp = parse_attribute("targetname=\"",
454 parts[i]))) {
455 g_free(auth->target);
456 auth->target = tmp;
458 else if ((tmp = parse_attribute("realm=\"",
459 parts[i]))) {
460 g_free(auth->realm);
461 auth->realm = tmp;
463 else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
464 g_free(auth->opaque);
465 auth->opaque = tmp;
467 i++;
469 g_strfreev(parts);
470 auth->nc = 1;
471 if (!strstr(hdr, "gssapi-data")) {
472 auth->nc = 1;
473 } else {
474 auth->nc = 3;
476 return;
479 if (!g_strncasecmp(hdr, "Kerberos", 8)) {
480 purple_debug(PURPLE_DEBUG_MISC, "sipe", "setting auth type to Kerberos (3)\r\n");
481 auth->type = AUTH_TYPE_KERBEROS;
482 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth - header: %s\r\n", hdr);
483 parts = g_strsplit(hdr+9, "\", ", 0);
484 i = 0;
485 while (parts[i]) {
486 purple_debug_info("sipe", "krb - parts[i] %s\n", parts[i]);
487 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
488 /*if (krb5_auth.gss_context == NULL) {
489 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
491 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
492 g_free(tmp);
494 if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
495 g_free(auth->target);
496 auth->target = tmp;
497 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
498 g_free(auth->realm);
499 auth->realm = tmp;
500 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
501 g_free(auth->opaque);
502 auth->opaque = tmp;
504 i++;
506 g_strfreev(parts);
507 auth->nc = 3;
508 return;
511 auth->type = AUTH_TYPE_DIGEST;
512 parts = g_strsplit(hdr, " ", 0);
513 while (parts[i]) {
514 if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
515 g_free(auth->nonce);
516 auth->nonce = tmp;
518 else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
519 g_free(auth->realm);
520 auth->realm = tmp;
522 i++;
524 g_strfreev(parts);
526 purple_debug(PURPLE_DEBUG_MISC, "sipe", "nonce: %s realm: %s\n", auth->nonce ? auth->nonce : "(null)", auth->realm ? auth->realm : "(null)");
527 if (auth->realm) {
528 g_free(auth->digest_session_key);
529 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
530 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
532 auth->nc = 1;
536 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
538 PurpleConnection *gc = data;
539 struct sipe_account_data *sip = gc->proto_data;
540 gsize max_write;
541 gssize written;
543 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
545 if (max_write == 0) {
546 if (sip->tx_handler != 0){
547 purple_input_remove(sip->tx_handler);
548 sip->tx_handler = 0;
550 return;
553 written = write(sip->fd, sip->txbuf->outptr, max_write);
555 if (written < 0 && errno == EAGAIN)
556 written = 0;
557 else if (written <= 0) {
558 /*TODO: do we really want to disconnect on a failure to write?*/
559 purple_connection_error(gc, _("Could not write"));
560 return;
563 purple_circ_buffer_mark_read(sip->txbuf, written);
566 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
568 PurpleConnection *gc = data;
569 struct sipe_account_data *sip = gc->proto_data;
570 gsize max_write;
571 gssize written;
573 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
575 if (max_write == 0) {
576 if (sip->tx_handler != 0) {
577 purple_input_remove(sip->tx_handler);
578 sip->tx_handler = 0;
579 return;
583 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
585 if (written < 0 && errno == EAGAIN)
586 written = 0;
587 else if (written <= 0) {
588 /*TODO: do we really want to disconnect on a failure to write?*/
589 purple_connection_error(gc, _("Could not write"));
590 return;
593 purple_circ_buffer_mark_read(sip->txbuf, written);
596 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
598 static void send_later_cb(gpointer data, gint source, const gchar *error)
600 PurpleConnection *gc = data;
601 struct sipe_account_data *sip;
602 struct sip_connection *conn;
604 if (!PURPLE_CONNECTION_IS_VALID(gc))
606 if (source >= 0)
607 close(source);
608 return;
611 if (source < 0) {
612 purple_connection_error(gc, _("Could not connect"));
613 return;
616 sip = gc->proto_data;
617 sip->fd = source;
618 sip->connecting = FALSE;
619 sip->last_keepalive = time(NULL);
621 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
623 /* If there is more to write now, we need to register a handler */
624 if (sip->txbuf->bufused > 0)
625 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
627 conn = connection_create(sip, source);
628 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
631 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
633 struct sipe_account_data *sip;
634 struct sip_connection *conn;
636 if (!PURPLE_CONNECTION_IS_VALID(gc))
638 if (gsc) purple_ssl_close(gsc);
639 return NULL;
642 sip = gc->proto_data;
643 sip->fd = gsc->fd;
644 sip->gsc = gsc;
645 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
646 sip->connecting = FALSE;
647 sip->last_keepalive = time(NULL);
649 conn = connection_create(sip, gsc->fd);
651 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
653 return sip;
656 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
658 PurpleConnection *gc = data;
659 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
660 if (sip == NULL) return;
662 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
664 /* If there is more to write now */
665 if (sip->txbuf->bufused > 0) {
666 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
671 static void sendlater(PurpleConnection *gc, const char *buf)
673 struct sipe_account_data *sip = gc->proto_data;
675 if (!sip->connecting) {
676 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
677 if (sip->transport == SIPE_TRANSPORT_TLS){
678 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
679 } else {
680 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
681 purple_connection_error(gc, _("Couldn't create socket"));
684 sip->connecting = TRUE;
687 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
688 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
690 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
693 static void sendout_pkt(PurpleConnection *gc, const char *buf)
695 struct sipe_account_data *sip = gc->proto_data;
696 time_t currtime = time(NULL);
697 int writelen = strlen(buf);
699 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
700 if (sip->transport == SIPE_TRANSPORT_UDP) {
701 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
702 purple_debug_info("sipe", "could not send packet\n");
704 } else {
705 int ret;
706 if (sip->fd < 0) {
707 sendlater(gc, buf);
708 return;
711 if (sip->tx_handler) {
712 ret = -1;
713 errno = EAGAIN;
714 } else{
715 if (sip->gsc){
716 ret = purple_ssl_write(sip->gsc, buf, writelen);
717 }else{
718 ret = write(sip->fd, buf, writelen);
722 if (ret < 0 && errno == EAGAIN)
723 ret = 0;
724 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
725 sendlater(gc, buf);
726 return;
729 if (ret < writelen) {
730 if (!sip->tx_handler){
731 if (sip->gsc){
732 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
734 else{
735 sip->tx_handler = purple_input_add(sip->fd,
736 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
737 gc);
741 /* XXX: is it OK to do this? You might get part of a request sent
742 with part of another. */
743 if (sip->txbuf->bufused > 0)
744 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
746 purple_circ_buffer_append(sip->txbuf, buf + ret,
747 writelen - ret);
752 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
754 sendout_pkt(gc, buf);
755 return len;
758 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
760 GSList *tmp = msg->headers;
761 gchar *name;
762 gchar *value;
763 GString *outstr = g_string_new("");
764 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
765 while (tmp) {
766 name = ((struct siphdrelement*) (tmp->data))->name;
767 value = ((struct siphdrelement*) (tmp->data))->value;
768 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
769 tmp = g_slist_next(tmp);
771 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
772 sendout_pkt(sip->gc, outstr->str);
773 g_string_free(outstr, TRUE);
776 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
778 gchar * buf;
779 if (sip->registrar.ntlm_key) {
780 struct sipmsg_breakdown msgbd;
781 gchar *signature_input_str;
782 msgbd.msg = msg;
783 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
784 msgbd.rand = g_strdup_printf("%08x", g_random_int());
785 sip->registrar.ntlm_num++;
786 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
787 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
788 if (signature_input_str != NULL) {
789 msg->signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
790 msg->rand = g_strdup(msgbd.rand);
791 msg->num = g_strdup(msgbd.num);
792 g_free(signature_input_str);
794 sipmsg_breakdown_free(&msgbd);
797 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
798 buf = auth_header(sip, &sip->registrar, msg);
799 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
800 sipmsg_add_header(msg, "Authorization", buf);
801 } else {
802 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
804 g_free(buf);
805 } 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")) {
806 sip->registrar.nc = 3;
807 sip->registrar.type = AUTH_TYPE_NTLM;
809 buf = auth_header(sip, &sip->registrar, msg);
810 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
811 g_free(buf);
812 } else {
813 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
817 static char *get_contact(struct sipe_account_data *sip)
819 return g_strdup(sip->contact);
823 * unused. Needed?
824 static char *get_contact_service(struct sipe_account_data *sip)
826 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()));
827 //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);
831 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
832 const char *text, const char *body)
834 gchar *name;
835 gchar *value;
836 GString *outstr = g_string_new("");
837 struct sipe_account_data *sip = gc->proto_data;
838 gchar *contact;
839 GSList *tmp;
841 sipmsg_remove_header(msg, "ms-user-data");
843 contact = get_contact(sip);
844 sipmsg_remove_header(msg, "Contact");
845 sipmsg_add_header(msg, "Contact", contact);
846 g_free(contact);
848 /* When sending the acknowlegements and errors, the content length from the original
849 message is still here, but there is no body; we need to make sure we're sending the
850 correct content length */
851 sipmsg_remove_header(msg, "Content-Length");
852 if (body) {
853 gchar len[12];
854 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
855 sipmsg_add_header(msg, "Content-Length", len);
856 } else {
857 sipmsg_remove_header(msg, "Content-Type");
858 sipmsg_add_header(msg, "Content-Length", "0");
861 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
862 //gchar * mic = "MICTODO";
863 msg->response = code;
865 sipmsg_remove_header(msg, "Authentication-Info");
866 sign_outgoing_message(msg, sip, msg->method);
868 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
869 tmp = msg->headers;
870 while (tmp) {
871 name = ((struct siphdrelement*) (tmp->data))->name;
872 value = ((struct siphdrelement*) (tmp->data))->value;
874 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
875 tmp = g_slist_next(tmp);
877 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
878 sendout_pkt(gc, outstr->str);
879 g_string_free(outstr, TRUE);
882 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
884 if (trans->msg) sipmsg_free(trans->msg);
885 sip->transactions = g_slist_remove(sip->transactions, trans);
886 g_free(trans);
889 static struct transaction *
890 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
892 struct transaction *trans = g_new0(struct transaction, 1);
893 trans->time = time(NULL);
894 trans->msg = (struct sipmsg *)msg;
895 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
896 trans->callback = callback;
897 sip->transactions = g_slist_append(sip->transactions, trans);
898 return trans;
901 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
903 struct transaction *trans;
904 GSList *transactions = sip->transactions;
905 gchar *cseq = sipmsg_find_header(msg, "CSeq");
907 while (transactions) {
908 trans = transactions->data;
909 if (!strcmp(trans->cseq, cseq)) {
910 return trans;
912 transactions = transactions->next;
915 return NULL;
918 static struct transaction *
919 send_sip_request(PurpleConnection *gc, const gchar *method,
920 const gchar *url, const gchar *to, const gchar *addheaders,
921 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
923 struct sipe_account_data *sip = gc->proto_data;
924 const char *addh = "";
925 char *buf;
926 struct sipmsg *msg;
927 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
928 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
929 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
930 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
931 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
932 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
933 gchar *route = strdup("");
934 gchar *epid = get_epid(sip); // TODO generate one per account/login
935 struct transaction *trans;
937 if (dialog && dialog->routes)
939 GSList *iter = dialog->routes;
941 while(iter)
943 char *tmp = route;
944 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
945 g_free(tmp);
946 iter = g_slist_next(iter);
950 if (!ourtag && !dialog) {
951 ourtag = gentag();
954 if (!strcmp(method, "REGISTER")) {
955 if (sip->regcallid) {
956 g_free(callid);
957 callid = g_strdup(sip->regcallid);
958 } else {
959 sip->regcallid = g_strdup(callid);
963 if (addheaders) addh = addheaders;
965 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
966 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
967 "From: <sip:%s>%s%s;epid=%s\r\n"
968 "To: <%s>%s%s%s%s\r\n"
969 "Max-Forwards: 70\r\n"
970 "CSeq: %d %s\r\n"
971 "User-Agent: %s\r\n"
972 "Call-ID: %s\r\n"
973 "%s%s"
974 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
975 method,
976 dialog && dialog->request ? dialog->request : url,
977 TRANSPORT_DESCRIPTOR,
978 purple_network_get_my_ip(-1),
979 sip->listenport,
980 branch ? ";branch=" : "",
981 branch ? branch : "",
982 sip->username,
983 ourtag ? ";tag=" : "",
984 ourtag ? ourtag : "",
985 epid,
987 theirtag ? ";tag=" : "",
988 theirtag ? theirtag : "",
989 theirepid ? ";epid=" : "",
990 theirepid ? theirepid : "",
991 dialog ? ++dialog->cseq : ++sip->cseq,
992 method,
993 useragent,
994 callid,
995 route,
996 addh,
997 body ? strlen(body) : 0,
998 body ? body : "");
1001 //printf ("parsing msg buf:\n%s\n\n", buf);
1002 msg = sipmsg_parse_msg(buf);
1004 g_free(buf);
1005 g_free(ourtag);
1006 g_free(theirtag);
1007 g_free(theirepid);
1008 g_free(branch);
1009 g_free(callid);
1010 g_free(route);
1011 g_free(epid);
1013 sign_outgoing_message (msg, sip, method);
1015 buf = sipmsg_to_string (msg);
1017 /* add to ongoing transactions */
1018 trans = transactions_add_buf(sip, msg, tc);
1019 sendout_pkt(gc, buf);
1020 g_free(buf);
1022 return trans;
1025 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
1027 gchar *from = g_strdup_printf("sip:%s", sip->username);
1028 gchar *contact = get_contact(sip);
1029 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
1030 "Content-Type: application/SOAP+xml\r\n",contact);
1032 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
1033 tr->payload = payload;
1035 g_free(from);
1036 g_free(contact);
1037 g_free(hdr);
1040 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
1042 send_soap_request_with_cb(sip, body, NULL, NULL);
1045 static char *get_contact_register(struct sipe_account_data *sip)
1047 char *epid = get_epid(sip);
1048 char *uuid = generateUUIDfromEPID(epid);
1049 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);
1050 g_free(uuid);
1051 g_free(epid);
1052 return(buf);
1055 static void do_register_exp(struct sipe_account_data *sip, int expire)
1057 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
1058 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
1059 char *to = g_strdup_printf("sip:%s", sip->username);
1060 char *contact = get_contact_register(sip);
1061 char *hdr = g_strdup_printf("Contact: %s\r\n"
1062 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1063 "Event: registration\r\n"
1064 "Allow-Events: presence\r\n"
1065 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1066 "%s", contact, expires);
1067 g_free(contact);
1068 g_free(expires);
1070 sip->registerstatus = 1;
1072 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
1073 process_register_response);
1075 g_free(hdr);
1076 g_free(uri);
1077 g_free(to);
1080 static void do_register_cb(struct sipe_account_data *sip)
1082 do_register_exp(sip, -1);
1083 sip->reregister_set = FALSE;
1086 static void do_register(struct sipe_account_data *sip)
1088 do_register_exp(sip, -1);
1092 * Returns URI from provided To or From header.
1094 * Needs to g_free() after use.
1096 * @return URI with sip: prefix
1098 static gchar *parse_from(const gchar *hdr)
1100 gchar *from;
1101 const gchar *tmp, *tmp2 = hdr;
1103 if (!hdr) return NULL;
1104 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
1105 tmp = strchr(hdr, '<');
1107 /* i hate the different SIP UA behaviours... */
1108 if (tmp) { /* sip address in <...> */
1109 tmp2 = tmp + 1;
1110 tmp = strchr(tmp2, '>');
1111 if (tmp) {
1112 from = g_strndup(tmp2, tmp - tmp2);
1113 } else {
1114 purple_debug_info("sipe", "found < without > in From\n");
1115 return NULL;
1117 } else {
1118 tmp = strchr(tmp2, ';');
1119 if (tmp) {
1120 from = g_strndup(tmp2, tmp - tmp2);
1121 } else {
1122 from = g_strdup(tmp2);
1125 purple_debug_info("sipe", "got %s\n", from);
1126 return from;
1129 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1131 va_list args;
1132 xmlnode * node = NULL;
1133 const gchar * name;
1135 va_start(args, parent);
1136 while ((name = va_arg(args, const char *)) != NULL) {
1137 node = xmlnode_get_child(parent, name);
1138 if (node == NULL) return NULL;
1139 parent = node;
1141 va_end(args);
1143 return node;
1147 static void
1148 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1150 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1151 send_soap_request(sip, body);
1152 g_free(body);
1155 static void
1156 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1158 if (allow) {
1159 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1160 } else {
1161 purple_debug_info("sipe", "Blocking contact %s\n", who);
1164 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1167 static
1168 void sipe_auth_user_cb(void * data)
1170 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1171 if (!job) return;
1173 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1174 g_free(job);
1177 static
1178 void sipe_deny_user_cb(void * data)
1180 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1181 if (!job) return;
1183 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1184 g_free(job);
1187 static void
1188 sipe_add_permit(PurpleConnection *gc, const char *name)
1190 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1191 sipe_contact_allow_deny(sip, name, TRUE);
1194 static void
1195 sipe_add_deny(PurpleConnection *gc, const char *name)
1197 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1198 sipe_contact_allow_deny(sip, name, FALSE);
1201 /*static void
1202 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1204 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1205 sipe_contact_set_acl(sip, name, "");
1208 static void
1209 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1211 xmlnode *watchers;
1212 xmlnode *watcher;
1213 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1214 if (msg->response != 0 && msg->response != 200) return;
1216 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1218 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1219 if (!watchers) return;
1221 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1222 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1223 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1224 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1226 // TODO pull out optional displayName to pass as alias
1227 if (remote_user) {
1228 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1229 job->who = remote_user;
1230 job->sip = sip;
1231 purple_account_request_authorization(
1232 sip->account,
1233 remote_user,
1234 NULL, // id
1235 alias,
1236 NULL, // message
1237 on_list,
1238 sipe_auth_user_cb,
1239 sipe_deny_user_cb,
1240 (void *) job);
1245 xmlnode_free(watchers);
1246 return;
1249 static void
1250 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1252 PurpleGroup * purple_group = purple_find_group(group->name);
1253 if (!purple_group) {
1254 purple_group = purple_group_new(group->name);
1255 purple_blist_add_group(purple_group, NULL);
1258 if (purple_group) {
1259 group->purple_group = purple_group;
1260 sip->groups = g_slist_append(sip->groups, group);
1261 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1262 } else {
1263 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1267 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1269 struct sipe_group *group;
1270 GSList *entry;
1271 if (sip == NULL) {
1272 return NULL;
1275 entry = sip->groups;
1276 while (entry) {
1277 group = entry->data;
1278 if (group->id == id) {
1279 return group;
1281 entry = entry->next;
1283 return NULL;
1286 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1288 struct sipe_group *group;
1289 GSList *entry;
1290 if (sip == NULL) {
1291 return NULL;
1294 entry = sip->groups;
1295 while (entry) {
1296 group = entry->data;
1297 if (!strcmp(group->name, name)) {
1298 return group;
1300 entry = entry->next;
1302 return NULL;
1305 static void
1306 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1308 gchar *body;
1309 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1310 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1311 send_soap_request(sip, body);
1312 g_free(body);
1313 g_free(group->name);
1314 group->name = g_strdup(name);
1318 * Only appends if no such value already stored.
1319 * Like Set in Java.
1321 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1322 GSList * res = list;
1323 if (!g_slist_find_custom(list, data, func)) {
1324 res = g_slist_insert_sorted(list, data, func);
1326 return res;
1329 static int
1330 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1331 return group1->id - group2->id;
1335 * Returns string like "2 4 7 8" - group ids buddy belong to.
1337 static gchar *
1338 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1339 int i = 0;
1340 gchar *res;
1341 //creating array from GList, converting int to gchar*
1342 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1343 GSList *entry = buddy->groups;
1344 while (entry) {
1345 struct sipe_group * group = entry->data;
1346 ids_arr[i] = g_strdup_printf("%d", group->id);
1347 entry = entry->next;
1348 i++;
1350 ids_arr[i] = NULL;
1351 res = g_strjoinv(" ", ids_arr);
1352 g_strfreev(ids_arr);
1353 return res;
1357 * Sends buddy update to server
1359 static void
1360 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1362 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1363 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1365 if (buddy && purple_buddy) {
1366 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1367 gchar *body;
1368 gchar *groups = sipe_get_buddy_groups_string(buddy);
1369 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1371 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1372 alias, groups, "true", buddy->name, sip->contacts_delta++
1374 send_soap_request(sip, body);
1375 g_free(groups);
1376 g_free(body);
1380 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1382 if (msg->response == 200) {
1383 struct sipe_group *group;
1384 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1385 xmlnode *xml;
1386 xmlnode *node;
1387 char *group_id;
1388 struct sipe_buddy *buddy;
1390 xml = xmlnode_from_str(msg->body, msg->bodylen);
1391 if (!xml) {
1392 g_free(ctx);
1393 return FALSE;
1396 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1397 if (!node) {
1398 g_free(ctx);
1399 xmlnode_free(xml);
1400 return FALSE;
1403 group_id = xmlnode_get_data(node);
1404 if (!group_id) {
1405 g_free(ctx);
1406 xmlnode_free(xml);
1407 return FALSE;
1410 group = g_new0(struct sipe_group, 1);
1411 group->id = (int)g_ascii_strtod(group_id, NULL);
1412 g_free(group_id);
1413 group->name = ctx->group_name;
1415 sipe_group_add(sip, group);
1417 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1418 if (buddy) {
1419 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1422 sipe_group_set_user(sip, ctx->user_name);
1424 g_free(ctx);
1425 xmlnode_free(xml);
1426 return TRUE;
1428 return FALSE;
1431 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1433 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1434 gchar *body;
1435 ctx->group_name = g_strdup(name);
1436 ctx->user_name = g_strdup(who);
1438 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1439 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1440 g_free(body);
1444 * A timer callback
1445 * Should return FALSE if repetitive action is not needed
1447 gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1449 gboolean ret;
1450 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1451 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1452 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1453 (sched_action->action)(sched_action->sip, sched_action->payload);
1454 ret = sched_action->repetitive;
1455 g_free(sched_action->payload);
1456 g_free(sched_action->name);
1457 g_free(sched_action);
1458 return ret;
1462 * Do schedule action for execution in the future.
1463 * Non repetitive execution.
1465 * @param name of action (will be copied)
1466 * @param timeout in seconds
1467 * @action callback function
1468 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1470 void sipe_schedule_action(gchar *name, int timeout, Action action, struct sipe_account_data *sip, void * payload)
1472 struct scheduled_action *sched_action;
1474 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name, timeout);
1475 sched_action = g_new0(struct scheduled_action, 1);
1476 sched_action->repetitive = FALSE;
1477 sched_action->name = g_strdup(name);
1478 sched_action->action = action;
1479 sched_action->sip = sip;
1480 sched_action->payload = payload;
1481 sched_action->timeout_handler = purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1482 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1483 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1487 * Kills action timer effectively cancelling
1488 * scheduled action
1490 * @param name of action
1492 void sipe_cancel_scheduled_action(struct sipe_account_data *sip, gchar *name)
1494 GSList *entry;
1496 if (!sip->timeouts || !name) return;
1498 entry = sip->timeouts;
1499 while (entry) {
1500 struct scheduled_action *sched_action = entry->data;
1501 if(!strcmp(sched_action->name, name)) {
1502 GSList *to_delete = entry;
1503 entry = entry->next;
1504 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1505 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1506 purple_timeout_remove(sched_action->timeout_handler);
1507 g_free(sched_action->payload);
1508 g_free(sched_action->name);
1509 g_free(sched_action);
1510 } else {
1511 entry = entry->next;
1516 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1518 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1520 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1522 process_incoming_notify(sip, msg, FALSE, FALSE);
1524 return TRUE;
1527 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1529 gchar *tmp = *resources_uri;
1530 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1531 g_free(tmp);
1534 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1536 gchar *tmp = *resources_uri;
1537 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1538 g_free(tmp);
1542 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1543 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1544 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1545 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1546 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1549 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip){
1550 gchar *to = g_strdup_printf("sip:%s", sip->username);
1551 gchar *contact = get_contact(sip);
1552 gchar *request;
1553 gchar *content;
1554 gchar *resources_uri = g_strdup("");
1555 gchar *require = "";
1556 gchar *accept = "";
1557 gchar *autoextend = "";
1558 gchar *content_type;
1561 if (sip->msrtc_event_categories) {
1562 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1563 require = ", categoryList";
1564 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1565 content_type = "application/msrtc-adrl-categorylist+xml";
1566 content = g_strdup_printf(
1567 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1568 "<action name=\"subscribe\" id=\"63792024\">\n"
1569 "<adhocList>\n%s</adhocList>\n"
1570 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1571 "<category name=\"note\"/>\n"
1572 "<category name=\"state\"/>\n"
1573 "</categoryList>\n"
1574 "</action>\n"
1575 "</batchSub>", sip->username, resources_uri);
1576 } else {
1577 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1578 autoextend = "Supported: com.microsoft.autoextend\r\n";
1579 content_type = "application/adrl+xml";
1580 content = g_strdup_printf(
1581 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1582 "<create xmlns=\"\">\n%s</create>\n"
1583 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1585 g_free(resources_uri);
1587 request = g_strdup_printf(
1588 "Require: adhoclist%s\r\n"
1589 "Supported: eventlist\r\n"
1590 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1591 "Supported: ms-piggyback-first-notify\r\n"
1592 "%sSupported: ms-benotify\r\n"
1593 "Proxy-Require: ms-benotify\r\n"
1594 "Event: presence\r\n"
1595 "Content-Type: %s\r\n"
1596 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1597 g_free(contact);
1599 /* subscribe to buddy presence */
1600 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1601 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1603 g_free(content);
1604 g_free(to);
1605 g_free(request);
1609 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1610 * The user sends a single SUBSCRIBE request to the subscribed contact.
1611 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1615 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, const char * buddy_name)
1617 gchar *to = strstr(buddy_name, "sip:") ? g_strdup(buddy_name) : g_strdup_printf("sip:%s", buddy_name);
1618 gchar *tmp = get_contact(sip);
1619 gchar *request;
1620 gchar *content;
1621 gchar *autoextend = "";
1623 if (!sip->msrtc_event_categories)
1624 autoextend = "Supported: com.microsoft.autoextend\r\n";
1626 request = g_strdup_printf(
1627 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1628 "Supported: ms-piggyback-first-notify\r\n"
1629 "%sSupported: ms-benotify\r\n"
1630 "Proxy-Require: ms-benotify\r\n"
1631 "Event: presence\r\n"
1632 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1633 "Contact: %s\r\n", autoextend,tmp);
1635 content = g_strdup_printf(
1636 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1637 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1638 "<resource uri=\"%s\"/>\n"
1639 "</adhocList>\n"
1640 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1641 "<category name=\"note\"/>\n"
1642 "<category name=\"state\"/>\n"
1643 "</categoryList>\n"
1644 "</action>\n"
1645 "</batchSub>", sip->username, to
1648 g_free(tmp);
1650 /* subscribe to buddy presence */
1651 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1653 g_free(content);
1654 g_free(to);
1655 g_free(request);
1658 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1660 if (!purple_status_is_active(status))
1661 return;
1663 if (account->gc) {
1664 struct sipe_account_data *sip = account->gc->proto_data;
1666 if (sip) {
1667 g_free(sip->status);
1668 sip->status = g_strdup(purple_status_get_id(status));
1669 send_presence_status(sip);
1674 static void
1675 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1677 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1678 sipe_group_set_user(sip, name);
1681 static void
1682 sipe_group_buddy(PurpleConnection *gc,
1683 const char *who,
1684 const char *old_group_name,
1685 const char *new_group_name)
1687 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1688 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1689 struct sipe_group * old_group = NULL;
1690 struct sipe_group * new_group;
1692 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1693 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1695 if(!buddy) { // buddy not in roaming list
1696 return;
1699 if (old_group_name) {
1700 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1702 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1704 if (old_group) {
1705 buddy->groups = g_slist_remove(buddy->groups, old_group);
1706 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1709 if (!new_group) {
1710 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1711 } else {
1712 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1713 sipe_group_set_user(sip, who);
1717 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1719 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1720 struct sipe_buddy *b;
1722 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1724 // Prepend sip: if needed
1725 if (strncmp("sip:", buddy->name, 4)) {
1726 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1727 purple_blist_rename_buddy(buddy, buf);
1728 g_free(buf);
1731 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1732 b = g_new0(struct sipe_buddy, 1);
1733 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1734 b->name = g_strdup(buddy->name);
1735 g_hash_table_insert(sip->buddies, b->name, b);
1736 sipe_group_buddy(gc, b->name, NULL, group->name);
1737 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1738 } else {
1739 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1743 static void sipe_free_buddy(struct sipe_buddy *buddy)
1745 g_free(buddy->name);
1746 g_free(buddy->annotation);
1747 g_free(buddy->device_name);
1748 g_slist_free(buddy->groups);
1749 g_free(buddy);
1753 * Unassociates buddy from group first.
1754 * Then see if no groups left, removes buddy completely.
1755 * Otherwise updates buddy groups on server.
1757 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1759 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1760 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1761 struct sipe_group *g = NULL;
1763 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1765 if (!b) return;
1767 if (group) {
1768 g = sipe_group_find_by_name(sip, group->name);
1771 if (g) {
1772 b->groups = g_slist_remove(b->groups, g);
1773 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1776 if (g_slist_length(b->groups) < 1) {
1777 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", buddy->name);
1778 sipe_cancel_scheduled_action(sip, action_name);
1779 g_free(action_name);
1781 g_hash_table_remove(sip->buddies, buddy->name);
1783 if (b->name) {
1784 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1785 send_soap_request(sip, body);
1786 g_free(body);
1789 sipe_free_buddy(b);
1790 } else {
1791 //updates groups on server
1792 sipe_group_set_user(sip, b->name);
1797 static void
1798 sipe_rename_group(PurpleConnection *gc,
1799 const char *old_name,
1800 PurpleGroup *group,
1801 GList *moved_buddies)
1803 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1804 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1805 if (group) {
1806 sipe_group_rename(sip, s_group, group->name);
1807 } else {
1808 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1812 static void
1813 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1815 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1816 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1817 if (s_group) {
1818 gchar *body;
1819 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1820 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1821 send_soap_request(sip, body);
1822 g_free(body);
1824 sip->groups = g_slist_remove(sip->groups, s_group);
1825 g_free(s_group->name);
1826 g_free(s_group);
1827 } else {
1828 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1832 static GList *sipe_status_types(PurpleAccount *acc)
1834 PurpleStatusType *type;
1835 GList *types = NULL;
1837 // Online
1838 type = purple_status_type_new_with_attrs(
1839 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1840 // Translators: noun
1841 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1842 NULL);
1843 types = g_list_append(types, type);
1845 // Busy
1846 type = purple_status_type_new_with_attrs(
1847 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1848 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1849 NULL);
1850 types = g_list_append(types, type);
1852 // Do Not Disturb (not user settable)
1853 type = purple_status_type_new_with_attrs(
1854 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1855 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1856 NULL);
1857 types = g_list_append(types, type);
1859 // Be Right Back
1860 type = purple_status_type_new_with_attrs(
1861 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1862 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1863 NULL);
1864 types = g_list_append(types, type);
1866 // Away
1867 type = purple_status_type_new_with_attrs(
1868 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1869 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1870 NULL);
1871 types = g_list_append(types, type);
1873 //On The Phone
1874 type = purple_status_type_new_with_attrs(
1875 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1876 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1877 NULL);
1878 types = g_list_append(types, type);
1880 //Out To Lunch
1881 type = purple_status_type_new_with_attrs(
1882 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1883 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1884 NULL);
1885 types = g_list_append(types, type);
1887 //Appear Offline
1888 type = purple_status_type_new_full(
1889 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1890 types = g_list_append(types, type);
1892 // Offline
1893 type = purple_status_type_new_full(
1894 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1895 types = g_list_append(types, type);
1897 return types;
1901 * A callback for g_hash_table_foreach
1903 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1905 sipe_subscribe_presence_single(sip, buddy->name);
1909 * Removes entries from purple buddy list
1910 * that does not correspond ones in the roaming contact list.
1912 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1913 GSList *buddies = purple_find_buddies(sip->account, NULL);
1914 GSList *entry = buddies;
1915 struct sipe_buddy *buddy;
1916 PurpleBuddy *b;
1917 PurpleGroup *g;
1919 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1920 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1921 while (entry) {
1922 b = entry->data;
1923 g = purple_buddy_get_group(b);
1924 buddy = g_hash_table_lookup(sip->buddies, b->name);
1925 if(buddy) {
1926 gboolean in_sipe_groups = FALSE;
1927 GSList *entry2 = buddy->groups;
1928 while (entry2) {
1929 struct sipe_group *group = entry2->data;
1930 if (!strcmp(group->name, g->name)) {
1931 in_sipe_groups = TRUE;
1932 break;
1934 entry2 = entry2->next;
1936 if(!in_sipe_groups) {
1937 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1938 purple_blist_remove_buddy(b);
1940 } else {
1941 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1942 purple_blist_remove_buddy(b);
1944 entry = entry->next;
1948 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1950 int len = msg->bodylen;
1952 gchar *tmp = sipmsg_find_header(msg, "Event");
1953 xmlnode *item;
1954 xmlnode *isc;
1955 const gchar *contacts_delta;
1956 xmlnode *group_node;
1957 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1958 return FALSE;
1961 /* Convert the contact from XML to Purple Buddies */
1962 isc = xmlnode_from_str(msg->body, len);
1963 if (!isc) {
1964 return FALSE;
1967 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1968 if (contacts_delta) {
1969 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1972 /* Parse groups */
1973 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1974 struct sipe_group * group = g_new0(struct sipe_group, 1);
1975 const char *name = xmlnode_get_attrib(group_node, "name");
1977 if (!strncmp(name, "~", 1)) {
1978 // TODO translate
1979 name = "Other Contacts";
1981 group->name = g_strdup(name);
1982 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1984 sipe_group_add(sip, group);
1987 // Make sure we have at least one group
1988 if (g_slist_length(sip->groups) == 0) {
1989 struct sipe_group * group = g_new0(struct sipe_group, 1);
1990 PurpleGroup *purple_group;
1991 // TODO translate
1992 group->name = g_strdup("Other Contacts");
1993 group->id = 1;
1994 purple_group = purple_group_new(group->name);
1995 purple_blist_add_group(purple_group, NULL);
1996 sip->groups = g_slist_append(sip->groups, group);
1999 /* Parse contacts */
2000 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
2001 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
2002 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
2003 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
2004 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
2005 gchar **item_groups;
2006 struct sipe_group *group = NULL;
2007 struct sipe_buddy *buddy = NULL;
2008 int i = 0;
2010 // assign to group Other Contacts if nothing else received
2011 if(!groups || !strcmp("", groups) ) {
2012 group = sipe_group_find_by_name(sip, "Other Contacts");
2013 groups = group ? g_strdup_printf("%d", group->id) : "1";
2016 item_groups = g_strsplit(groups, " ", 0);
2018 while (item_groups[i]) {
2019 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2021 // If couldn't find the right group for this contact, just put them in the first group we have
2022 if (group == NULL && g_slist_length(sip->groups) > 0) {
2023 group = sip->groups->data;
2026 if (group != NULL) {
2027 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2028 if (!b){
2029 b = purple_buddy_new(sip->account, buddy_name, uri);
2030 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2033 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2034 if (name != NULL && strlen(name) != 0) {
2035 purple_blist_alias_buddy(b, name);
2039 if (!buddy) {
2040 buddy = g_new0(struct sipe_buddy, 1);
2041 buddy->name = g_strdup(b->name);
2042 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2045 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2047 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2048 } else {
2049 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2050 name);
2053 i++;
2054 } // while, contact groups
2055 g_strfreev(item_groups);
2056 g_free(groups);
2057 g_free(name);
2058 g_free(buddy_name);
2059 g_free(uri);
2061 } // for, contacts
2063 xmlnode_free(isc);
2065 sipe_cleanup_local_blist(sip);
2067 //subscribe to buddies
2068 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2069 //if(sip->msrtc_event_categories){
2070 sipe_subscribe_presence_batched(sip);
2071 //}else{
2072 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2074 sip->subscribed_buddies = TRUE;
2077 return 0;
2081 * Subscribe roaming contacts
2083 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2085 gchar *to = g_strdup_printf("sip:%s", sip->username);
2086 gchar *tmp = get_contact(sip);
2087 gchar *hdr = g_strdup_printf(
2088 "Event: vnd-microsoft-roaming-contacts\r\n"
2089 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2090 "Supported: com.microsoft.autoextend\r\n"
2091 "Supported: ms-benotify\r\n"
2092 "Proxy-Require: ms-benotify\r\n"
2093 "Supported: ms-piggyback-first-notify\r\n"
2094 "Contact: %s\r\n", tmp);
2095 g_free(tmp);
2097 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2098 g_free(to);
2099 g_free(hdr);
2102 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, struct sipmsg *msg)
2104 gchar *to = g_strdup_printf("sip:%s", sip->username);
2105 gchar *tmp = get_contact(sip);
2106 gchar *hdr = g_strdup_printf(
2107 "Event: presence.wpending\r\n"
2108 "Accept: text/xml+msrtc.wpending\r\n"
2109 "Supported: com.microsoft.autoextend\r\n"
2110 "Supported: ms-benotify\r\n"
2111 "Proxy-Require: ms-benotify\r\n"
2112 "Supported: ms-piggyback-first-notify\r\n"
2113 "Contact: %s\r\n", tmp);
2114 g_free(tmp);
2116 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2117 g_free(to);
2118 g_free(hdr);
2122 * Fires on deregistration event initiated by server.
2123 * [MS-SIPREGE] SIP extension.
2126 // 2007 Example
2128 // Content-Type: text/registration-event
2129 // subscription-state: terminated;expires=0
2130 // ms-diagnostics-public: 4141;reason="User disabled"
2132 // deregistered;event=rejected
2134 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2136 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2137 gchar *event = NULL;
2138 gchar *reason = NULL;
2139 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2141 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2142 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2144 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2145 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2146 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2147 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2148 } else {
2149 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2150 return;
2153 if (warning != NULL) {
2154 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2155 } else { // for LCS2005
2156 int error_id = 0;
2157 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2158 error_id = 4140; // [MS-SIPREGE]
2159 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2160 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2161 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2162 error_id = 4141;
2163 reason = g_strdup(_("User disabled")); // [MS-OCER]
2164 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2165 error_id = 4142;
2166 reason = g_strdup(_("User moved")); // [MS-OCER]
2169 g_free(event);
2170 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2171 g_free(reason);
2173 sip->gc->wants_to_die = TRUE;
2174 purple_connection_error(sip->gc, warning);
2175 g_free(warning);
2179 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2181 const gchar *contacts_delta;
2182 xmlnode *xml;
2184 xml = xmlnode_from_str(msg->body, msg->bodylen);
2185 if (!xml)
2187 return;
2190 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2191 if (contacts_delta)
2193 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2196 xmlnode_free(xml);
2202 * When we receive some self (BE) NOTIFY with a new subscriber
2203 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2207 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2209 gchar *contact;
2210 gchar *to;
2211 xmlnode *xml;
2212 xmlnode *node;
2213 char *display_name = NULL;
2214 PurpleBuddy *pbuddy;
2215 const char *alias;
2216 char *uri_alias;
2217 char *uri_user;
2219 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2221 xml = xmlnode_from_str(msg->body, msg->bodylen);
2222 if (!xml) return;
2224 contact = get_contact(sip);
2225 to = g_strdup_printf("sip:%s", sip->username);
2227 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2228 const char *user;
2229 const char *acknowledged;
2230 gchar *hdr;
2231 gchar *body;
2233 user = xmlnode_get_attrib(node, "user");
2234 if (!user) continue;
2235 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2236 uri_user = g_strdup_printf("sip:%s", user);
2237 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2238 if(pbuddy){
2239 alias = purple_buddy_get_local_alias(pbuddy);
2240 uri_alias = g_strdup_printf("sip:%s", alias);
2241 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2242 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2243 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2244 purple_blist_alias_buddy(pbuddy, display_name);
2246 g_free(uri_alias);
2248 g_free(uri_user);
2250 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2251 if(!g_ascii_strcasecmp(acknowledged,"false")){
2252 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2253 hdr = g_strdup_printf(
2254 "Contact: %s\r\n"
2255 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2257 body = g_strdup_printf(
2258 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2259 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2260 "</setSubscribers>", user);
2262 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2263 g_free(body);
2264 g_free(hdr);
2268 g_free(to);
2269 g_free(contact);
2270 xmlnode_free(xml);
2273 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2275 gchar *to = g_strdup_printf("sip:%s", sip->username);
2276 gchar *tmp = get_contact(sip);
2277 gchar *hdr = g_strdup_printf(
2278 "Event: vnd-microsoft-roaming-ACL\r\n"
2279 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2280 "Supported: com.microsoft.autoextend\r\n"
2281 "Supported: ms-benotify\r\n"
2282 "Proxy-Require: ms-benotify\r\n"
2283 "Supported: ms-piggyback-first-notify\r\n"
2284 "Contact: %s\r\n", tmp);
2285 g_free(tmp);
2287 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2288 g_free(to);
2289 g_free(hdr);
2293 * To request for presence information about the user, access level settings that have already been configured by the user
2294 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2295 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2298 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2300 gchar *to = g_strdup_printf("sip:%s", sip->username);
2301 gchar *tmp = get_contact(sip);
2302 gchar *hdr = g_strdup_printf(
2303 "Event: vnd-microsoft-roaming-self\r\n"
2304 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2305 "Supported: ms-benotify\r\n"
2306 "Proxy-Require: ms-benotify\r\n"
2307 "Supported: ms-piggyback-first-notify\r\n"
2308 "Contact: %s\r\n"
2309 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2311 gchar *body=g_strdup(
2312 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2313 "<roaming type=\"categories\"/>"
2314 "<roaming type=\"containers\"/>"
2315 "<roaming type=\"subscribers\"/></roamingList>");
2317 g_free(tmp);
2318 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2319 g_free(body);
2320 g_free(to);
2321 g_free(hdr);
2325 * For 2005 version
2327 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2329 gchar *to = g_strdup_printf("sip:%s", sip->username);
2330 gchar *tmp = get_contact(sip);
2331 gchar *hdr = g_strdup_printf(
2332 "Event: vnd-microsoft-provisioning\r\n"
2333 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2334 "Supported: com.microsoft.autoextend\r\n"
2335 "Supported: ms-benotify\r\n"
2336 "Proxy-Require: ms-benotify\r\n"
2337 "Supported: ms-piggyback-first-notify\r\n"
2338 "Expires: 0\r\n"
2339 "Contact: %s\r\n", tmp);
2341 g_free(tmp);
2342 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2343 g_free(to);
2344 g_free(hdr);
2347 /** Subscription for provisioning information to help with initial
2348 * configuration. This subscription is a one-time query (denoted by the Expires header,
2349 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2350 * configuration, meeting policies, and policy settings that Communicator must enforce.
2351 * TODO: for what we need this information.
2354 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2356 gchar *to = g_strdup_printf("sip:%s", sip->username);
2357 gchar *tmp = get_contact(sip);
2358 gchar *hdr = g_strdup_printf(
2359 "Event: vnd-microsoft-provisioning-v2\r\n"
2360 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2361 "Supported: com.microsoft.autoextend\r\n"
2362 "Supported: ms-benotify\r\n"
2363 "Proxy-Require: ms-benotify\r\n"
2364 "Supported: ms-piggyback-first-notify\r\n"
2365 "Expires: 0\r\n"
2366 "Contact: %s\r\n"
2367 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2368 gchar *body = g_strdup(
2369 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2370 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2371 "<provisioningGroup name=\"ucPolicy\"/>"
2372 "</provisioningGroupList>");
2374 g_free(tmp);
2375 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2376 g_free(body);
2377 g_free(to);
2378 g_free(hdr);
2381 /* IM Session (INVITE and MESSAGE methods) */
2383 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2385 struct sip_im_session *session;
2386 GSList *entry;
2387 if (sip == NULL || who == NULL) {
2388 return NULL;
2391 entry = sip->im_sessions;
2392 while (entry) {
2393 session = entry->data;
2394 if ((who != NULL && !strcmp(who, session->with))) {
2395 return session;
2397 entry = entry->next;
2399 return NULL;
2402 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2404 struct sip_im_session *session = find_im_session(sip, who);
2405 if (!session) {
2406 session = g_new0(struct sip_im_session, 1);
2407 session->with = g_strdup(who);
2408 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2409 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2411 return session;
2414 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2416 struct sip_dialog *dialog = session->dialog;
2417 GSList *entry;
2419 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2421 if (dialog) {
2422 entry = dialog->routes;
2423 while (entry) {
2424 g_free(entry->data);
2425 entry = g_slist_remove(entry, entry->data);
2427 entry = dialog->supported;
2428 while (entry) {
2429 g_free(entry->data);
2430 entry = g_slist_remove(entry, entry->data);
2432 g_free(dialog->callid);
2433 g_free(dialog->ourtag);
2434 g_free(dialog->theirtag);
2435 g_free(dialog->theirepid);
2436 g_free(dialog->request);
2438 g_free(session->dialog);
2440 entry = session->outgoing_message_queue;
2441 while (entry) {
2442 g_free(entry->data);
2443 entry = g_slist_remove(entry, entry->data);
2446 g_hash_table_destroy(session->unconfirmed_messages);
2448 g_free(session->with);
2449 g_free(session);
2452 static gboolean
2453 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2455 gboolean ret = TRUE;
2457 if (msg->response != 200) {
2458 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2459 return FALSE;
2462 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2464 return ret;
2468 * Asks UA/proxy about its capabilities.
2470 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2472 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2473 gchar *contact = get_contact(sip);
2474 gchar *request;
2475 request = g_strdup_printf(
2476 "Accept: application/sdp\r\n"
2477 "Contact: %s\r\n", contact);
2479 g_free(contact);
2481 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2483 g_free(to);
2484 g_free(request);
2487 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2489 char *msg, *msg_tmp;
2490 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2491 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2492 g_free(msg_tmp);
2493 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2494 "possibly because one or more persons are offline:\n%s") ,
2495 msg ? msg : "");
2496 purple_conv_present_error(with, sip->account, msg_tmp);
2497 g_free(msg);
2498 g_free(msg_tmp);
2501 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2503 static gboolean
2504 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2506 gboolean ret = TRUE;
2507 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2508 struct sip_im_session * session = find_im_session(sip, with);
2509 struct sip_dialog *dialog;
2510 gchar *cseq;
2511 char *key;
2512 gchar *message;
2514 if (!session) {
2515 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2516 g_free(with);
2517 return FALSE;
2520 dialog = session->dialog;
2521 if (!dialog) {
2522 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2523 g_free(with);
2524 return FALSE;
2527 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2528 key = g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq));
2529 g_free(cseq);
2530 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2532 if (msg->response != 200) {
2533 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2535 sipe_present_message_undelivered_err(with, sip, message);
2536 im_session_destroy(sip, session);
2537 ret = FALSE;
2538 } else {
2539 g_hash_table_remove(session->unconfirmed_messages, key);
2540 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2541 key, g_hash_table_size(session->unconfirmed_messages));
2544 g_free(key);
2545 g_free(with);
2547 sipe_im_process_queue(sip, session);
2548 return ret;
2551 static void sipe_send_message(struct sipe_account_data *sip, struct sip_im_session * session, const char *msg)
2553 gchar *hdr;
2554 gchar *fullto;
2555 gchar *tmp;
2556 char *msgformat;
2557 char *msgtext;
2558 gchar *msgr_value;
2559 gchar *msgr;
2561 if (strncmp("sip:", session->with, 4)) {
2562 fullto = g_strdup_printf("sip:%s", session->with);
2563 } else {
2564 fullto = g_strdup(session->with);
2567 sipe_parse_html(msg, &msgformat, &msgtext);
2568 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2570 msgr_value = sipmsg_get_msgr_string(msgformat);
2571 g_free(msgformat);
2572 if (msgr_value) {
2573 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2574 g_free(msgr_value);
2575 } else {
2576 msgr = g_strdup("");
2579 tmp = get_contact(sip);
2580 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2581 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2582 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2583 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2584 tmp, msgr);
2585 g_free(tmp);
2586 g_free(msgr);
2588 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msgtext, session->dialog, process_message_response);
2589 g_free(msgtext);
2590 g_free(hdr);
2591 g_free(fullto);
2595 static void
2596 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2598 GSList *entry = session->outgoing_message_queue;
2600 if (session->outgoing_invite) return; //do not send messages until INVITE responded.
2602 while (entry) {
2603 char *key = g_strdup_printf("<%s><%d><MESSAGE>", session->dialog->callid, (session->dialog->cseq) + 1);
2604 char *queued_msg = entry->data;
2605 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2606 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2607 key, g_hash_table_size(session->unconfirmed_messages));
2608 g_free(key);
2609 sipe_send_message(sip, session, queued_msg);
2610 entry = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2611 g_free(queued_msg);
2615 static void
2616 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2618 GSList *hdr = msg->headers;
2619 struct siphdrelement *elem;
2620 gchar *contact;
2622 while(hdr)
2624 elem = hdr->data;
2625 if(!g_ascii_strcasecmp(elem->name, "Record-Route"))
2627 gchar *route = sipmsg_find_part_of_header(elem->value, "<", ">", NULL);
2628 dialog->routes = g_slist_append(dialog->routes, route);
2630 hdr = g_slist_next(hdr);
2633 if (outgoing)
2635 dialog->routes = g_slist_reverse(dialog->routes);
2638 if (dialog->routes)
2640 dialog->request = dialog->routes->data;
2641 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2644 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2645 dialog->routes = g_slist_append(dialog->routes, contact);
2648 static void
2649 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2651 GSList *hdr = msg->headers;
2652 struct siphdrelement *elem;
2653 while(hdr)
2655 elem = hdr->data;
2656 if(!g_ascii_strcasecmp(elem->name, "Supported")
2657 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2659 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2662 hdr = g_slist_next(hdr);
2666 static void
2667 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2669 gchar *us = outgoing ? "From" : "To";
2670 gchar *them = outgoing ? "To" : "From";
2672 g_free(dialog->callid);
2673 g_free(dialog->ourtag);
2674 g_free(dialog->theirtag);
2676 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
2677 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2678 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2679 if (!dialog->theirepid) {
2680 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2681 if (!dialog->theirepid) {
2682 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2686 sipe_get_route_header(msg, dialog, outgoing);
2687 sipe_get_supported_header(msg, dialog, outgoing);
2691 static gboolean
2692 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2694 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2695 struct sip_im_session * session = find_im_session(sip, with);
2696 struct sip_dialog *dialog;
2697 char *cseq;
2698 char *key;
2699 gchar *message;
2701 if (!session) {
2702 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2703 g_free(with);
2704 return FALSE;
2707 dialog = session->dialog;
2708 if (!dialog) {
2709 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2710 g_free(with);
2711 return FALSE;
2714 sipe_parse_dialog(msg, dialog, TRUE);
2716 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2717 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2718 g_free(cseq);
2719 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2721 if (msg->response != 200) {
2722 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2724 sipe_present_message_undelivered_err(with, sip, message);
2725 im_session_destroy(sip, session);
2726 g_free(with);
2727 return FALSE;
2730 dialog->cseq = 0;
2731 send_sip_request(sip->gc, "ACK", session->with, session->with, NULL, NULL, dialog, NULL);
2732 session->outgoing_invite = NULL;
2733 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2734 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2735 if (session->outgoing_message_queue) {
2736 char *queued_msg = session->outgoing_message_queue->data;
2737 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2738 g_free(queued_msg);
2742 sipe_im_process_queue(sip, session);
2744 g_hash_table_remove(session->unconfirmed_messages, key);
2745 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2746 key, g_hash_table_size(session->unconfirmed_messages));
2748 g_free(key);
2749 g_free(with);
2750 return TRUE;
2754 static void sipe_invite(struct sipe_account_data *sip, struct sip_im_session *session, const gchar *msg_body)
2756 gchar *hdr;
2757 gchar *to;
2758 gchar *from;
2759 gchar *contact;
2760 gchar *body;
2761 char *msgformat;
2762 char *msgtext;
2763 char *base64_msg;
2764 char *ms_text_format;
2765 gchar *msgr_value;
2766 gchar *msgr;
2767 char *key;
2769 if (session->dialog) {
2770 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->with);
2771 return;
2774 session->dialog = g_new0(struct sip_dialog, 1);
2775 session->dialog->callid = gencallid();
2777 if (strstr(session->with, "sip:")) {
2778 to = g_strdup(session->with);
2779 } else {
2780 to = g_strdup_printf("sip:%s", session->with);
2783 sipe_parse_html(msg_body, &msgformat, &msgtext);
2784 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2786 msgr_value = sipmsg_get_msgr_string(msgformat);
2787 g_free(msgformat);
2788 msgr = "";
2789 if (msgr_value) {
2790 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2791 g_free(msgr_value);
2794 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2795 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2796 g_free(msgtext);
2797 g_free(msgr);
2798 g_free(base64_msg);
2800 key = g_strdup_printf("<%s><%d><INVITE>", session->dialog->callid, (session->dialog->cseq) + 1);
2801 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2802 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2803 key, g_hash_table_size(session->unconfirmed_messages));
2804 g_free(key);
2806 contact = get_contact(sip);
2807 from = g_strdup_printf("<sip:%s>", sip->username);
2808 hdr = g_strdup_printf(
2809 "Supported: ms-delayed-accept\r\n"
2810 "Roster-Manager: <%s>\r\n"
2811 "EndPoints: <%s>, <%s>\r\n"
2812 "Supported: com.microsoft.rtc-multiparty\r\n"
2813 "Contact: %s\r\n%s"
2814 "Content-Type: application/sdp\r\n",
2815 to, to, from, contact, ms_text_format);
2816 g_free(ms_text_format);
2818 body = g_strdup_printf(
2819 "v=0\r\n"
2820 "o=- 0 0 IN IP4 %s\r\n"
2821 "s=session\r\n"
2822 "c=IN IP4 %s\r\n"
2823 "t=0 0\r\n"
2824 "m=message %d sip null\r\n"
2825 "a=accept-types:text/plain text/html image/gif "
2826 "multipart/alternative application/im-iscomposing+xml\r\n",
2827 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
2829 session->outgoing_invite = send_sip_request(sip->gc, "INVITE",
2830 to, to, hdr, body, session->dialog, process_invite_response);
2832 g_free(to);
2833 g_free(from);
2834 g_free(body);
2835 g_free(hdr);
2836 g_free(contact);
2839 static void
2840 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
2842 if (session) {
2843 send_sip_request(sip->gc, "BYE", session->with, session->with, NULL, NULL, session->dialog, NULL);
2844 im_session_destroy(sip, session);
2848 static void
2849 sipe_convo_closed(PurpleConnection * gc, const char *who)
2851 struct sipe_account_data *sip = gc->proto_data;
2853 purple_debug_info("sipe", "conversation with %s closed\n", who);
2854 im_session_close(sip, find_im_session(sip, who));
2857 static void
2858 im_session_close_all (struct sipe_account_data *sip)
2860 GSList *entry = sip->im_sessions;
2861 while (entry) {
2862 im_session_close (sip, entry->data);
2863 entry = sip->im_sessions;
2867 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
2869 struct sipe_account_data *sip;
2870 gchar *to;
2871 struct sip_im_session *session;
2873 sip = gc->proto_data;
2874 to = g_strdup(who);
2876 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
2878 session = find_or_create_im_session(sip, who);
2880 // Queue the message
2881 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
2883 if (session->dialog && session->dialog->callid) {
2884 sipe_im_process_queue(sip, session);
2885 } else if (!session->outgoing_invite) {
2886 // Need to send the INVITE to get the outgoing dialog setup
2887 sipe_invite(sip, session, what);
2890 g_free(to);
2891 return 1;
2894 /* End IM Session (INVITE and MESSAGE methods) */
2896 static unsigned int
2897 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
2899 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
2900 struct sip_im_session *session;
2902 if (state == PURPLE_NOT_TYPING)
2903 return 0;
2905 session = find_im_session(sip, who);
2907 if (session && session->dialog) {
2908 send_sip_request(gc, "INFO", who, who,
2909 "Content-Type: application/xml\r\n",
2910 SIPE_SEND_TYPING, session->dialog, NULL);
2912 return SIPE_TYPING_SEND_TIMEOUT;
2915 static gboolean resend_timeout(struct sipe_account_data *sip)
2917 GSList *tmp = sip->transactions;
2918 time_t currtime = time(NULL);
2919 while (tmp) {
2920 struct transaction *trans = tmp->data;
2921 tmp = tmp->next;
2922 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime-trans->time);
2923 if ((currtime - trans->time > 5) && trans->retries >= 1) {
2924 /* TODO 408 */
2925 } else {
2926 if ((currtime - trans->time > 2) && trans->retries == 0) {
2927 trans->retries++;
2928 sendout_sipmsg(sip, trans->msg);
2932 return TRUE;
2935 static void do_reauthenticate_cb(struct sipe_account_data *sip)
2937 /* register again when security token expires */
2938 /* we have to start a new authentication as the security token
2939 * is almost expired by sending a not signed REGISTER message */
2940 purple_debug_info("sipe", "do a full reauthentication\n");
2941 sipe_auth_free(&sip->registrar);
2942 sip->registerstatus = 0;
2943 do_register(sip);
2944 sip->reauthenticate_set = FALSE;
2947 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
2949 gchar *from;
2950 gchar *contenttype;
2951 gboolean found = FALSE;
2953 from = parse_from(sipmsg_find_header(msg, "From"));
2955 if (!from) return;
2957 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
2959 contenttype = sipmsg_find_header(msg, "Content-Type");
2960 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
2962 gchar *html = get_html_message(contenttype, msg->body);
2963 serv_got_im(sip->gc, from, html, 0, time(NULL));
2964 g_free(html);
2965 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2966 found = TRUE;
2968 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
2969 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
2970 xmlnode *state;
2971 gchar *statedata;
2973 if (!isc) {
2974 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2975 return;
2978 state = xmlnode_get_child(isc, "state");
2980 if (!state) {
2981 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2982 xmlnode_free(isc);
2983 return;
2986 statedata = xmlnode_get_data(state);
2987 if (statedata) {
2988 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
2989 else serv_got_typing_stopped(sip->gc, from);
2991 g_free(statedata);
2993 xmlnode_free(isc);
2994 send_sip_response(sip->gc, msg, 200, "OK", NULL);
2995 found = TRUE;
2997 if (!found) {
2998 purple_debug_info("sipe", "got unknown mime-type");
2999 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3001 g_free(from);
3004 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3006 gchar *ms_text_format;
3007 gchar *from;
3008 gchar *body;
3009 struct sip_im_session *session;
3011 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3013 // Only accept text invitations
3014 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3015 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3016 return;
3019 from = parse_from(sipmsg_find_header(msg, "From"));
3020 session = find_or_create_im_session (sip, from);
3021 if (session) {
3022 if (session->dialog) {
3023 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3024 } else {
3025 session->dialog = g_new0(struct sip_dialog, 1);
3027 sipe_parse_dialog(msg, session->dialog, FALSE);
3029 session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3030 session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3031 session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3032 session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3034 } else {
3035 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3038 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3039 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3040 if (ms_text_format) {
3041 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3043 gchar *html = get_html_message(ms_text_format, NULL);
3044 if (html) {
3045 serv_got_im(sip->gc, from, html, 0, time(NULL));
3046 g_free(html);
3047 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3051 g_free(from);
3053 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3054 sipmsg_remove_header(msg, "Ms-Text-Format");
3055 sipmsg_remove_header(msg, "EndPoints");
3056 sipmsg_remove_header(msg, "User-Agent");
3057 sipmsg_remove_header(msg, "Roster-Manager");
3059 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3060 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3062 body = g_strdup_printf(
3063 "v=0\r\n"
3064 "o=- 0 0 IN IP4 %s\r\n"
3065 "s=session\r\n"
3066 "c=IN IP4 %s\r\n"
3067 "t=0 0\r\n"
3068 "m=message %d sip sip:%s\r\n"
3069 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3070 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3071 sip->realport, sip->username);
3072 send_sip_response(sip->gc, msg, 200, "OK", body);
3073 g_free(body);
3076 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3078 gchar *body;
3080 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3081 sipmsg_remove_header(msg, "EndPoints");
3082 sipmsg_remove_header(msg, "User-Agent");
3084 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3085 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3087 body = g_strdup_printf(
3088 "v=0\r\n"
3089 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3090 "s=session\r\n"
3091 "c=IN IP4 0.0.0.0\r\n"
3092 "t=0 0\r\n"
3093 "m=message %d sip sip:%s\r\n"
3094 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3095 sip->realport, sip->username);
3096 send_sip_response(sip->gc, msg, 200, "OK", body);
3097 g_free(body);
3100 static void sipe_connection_cleanup(struct sipe_account_data *);
3101 static void create_connection(struct sipe_account_data *, gchar *, int);
3103 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3105 gchar *tmp;
3106 gchar *timeout;
3107 const gchar *expires_header;
3108 int expires, i;
3109 GSList *hdr = msg->headers;
3110 GSList *entry;
3111 struct siphdrelement *elem;
3113 expires_header = sipmsg_find_header(msg, "Expires");
3114 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3115 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3117 switch (msg->response) {
3118 case 200:
3119 if (expires == 0) {
3120 sip->registerstatus = 0;
3121 } else {
3122 gchar *contact_hdr = NULL;
3123 gchar *gruu = NULL;
3124 gchar *epid;
3125 gchar *uuid;
3127 if (!sip->reregister_set) {
3128 gchar *action_name = g_strdup_printf("<%s>", "registration");
3129 sipe_schedule_action(action_name, expires, (Action) do_register_cb, sip, NULL);
3130 g_free(action_name);
3131 sip->reregister_set = TRUE;
3134 sip->registerstatus = 3;
3136 if (!sip->reauthenticate_set) {
3137 /* we have to reauthenticate as our security token expires
3138 after eight hours (be five minutes early) */
3139 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3140 guint reauth_timeout = (8 * 3600) - 360;
3141 sipe_schedule_action(action_name, reauth_timeout, (Action) do_reauthenticate_cb, sip, NULL);
3142 g_free(action_name);
3143 sip->reauthenticate_set = TRUE;
3146 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3148 epid = get_epid(sip);
3149 uuid = generateUUIDfromEPID(epid);
3150 g_free(epid);
3152 // There can be multiple Contact headers (one per location where the user is logged in) so
3153 // make sure to only get the one for this uuid
3154 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3155 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3156 if (valid_contact) {
3157 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3158 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3159 g_free(valid_contact);
3160 break;
3161 } else {
3162 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3165 g_free(uuid);
3167 g_free(sip->contact);
3168 if(gruu) {
3169 sip->contact = g_strdup_printf("<%s>", gruu);
3170 g_free(gruu);
3171 } else {
3172 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3173 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);
3175 sip->msrtc_event_categories = FALSE;
3177 while(hdr)
3179 elem = hdr->data;
3180 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3181 if (strstr(elem->value, "msrtc-event-categories")) {
3182 sip->msrtc_event_categories = TRUE;
3184 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s, %d\n", elem->value, sip->msrtc_event_categories);
3186 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3187 gchar **caps = g_strsplit(elem->value,",",0);
3188 i = 0;
3189 while (caps[i]) {
3190 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3191 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3192 i++;
3194 g_strfreev(caps);
3196 hdr = g_slist_next(hdr);
3199 if (!sip->subscribed) { //do it just once, not every re-register
3200 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3201 sipe_options_request(sip, sip->sipdomain);
3203 entry = sip->allow_events;
3204 while (entry) {
3205 tmp = entry->data;
3206 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3207 sipe_subscribe_roaming_contacts(sip, msg);
3209 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3210 sipe_subscribe_roaming_acl(sip, msg);
3212 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3213 sipe_subscribe_roaming_self(sip, msg);
3215 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3216 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3217 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3218 sipe_subscribe_roaming_provisioning(sip, msg);
3220 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3221 sipe_subscribe_presence_wpending(sip, msg);
3223 entry = entry->next;
3225 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3226 sip->subscribed = TRUE;
3229 /*if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
3230 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
3231 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
3232 } else {*/
3233 tmp = sipmsg_find_header(msg, "ms-keep-alive");
3234 timeout = sipmsg_find_part_of_header(tmp, "timeout=", ";", NULL);
3235 if (timeout) {
3236 sipe_keep_alive_timeout(sip, timeout);
3238 else{
3239 sipe_keep_alive_timeout(sip, g_strdup("300"));
3241 /*}*/
3243 // Should we remove the transaction here?
3244 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3245 transactions_remove(sip, tc);
3247 break;
3248 case 301:
3250 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3252 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3253 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3254 gchar **tmp;
3255 gchar *hostname;
3256 int port = 0;
3257 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3258 int i = 1;
3260 tmp = g_strsplit(parts[0], ":", 0);
3261 hostname = g_strdup(tmp[0]);
3262 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3263 g_strfreev(tmp);
3265 while (parts[i]) {
3266 tmp = g_strsplit(parts[i], "=", 0);
3267 if (tmp[1]) {
3268 if (g_strcasecmp("transport", tmp[0]) == 0) {
3269 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3270 transport = SIPE_TRANSPORT_TCP;
3271 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3272 transport = SIPE_TRANSPORT_UDP;
3276 g_strfreev(tmp);
3277 i++;
3279 g_strfreev(parts);
3281 /* Close old connection */
3282 sipe_connection_cleanup(sip);
3284 /* Create new connection */
3285 sip->transport = transport;
3286 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3287 hostname, port, TRANSPORT_DESCRIPTOR);
3288 create_connection(sip, hostname, port);
3290 g_free(redirect);
3292 break;
3293 case 401:
3294 if (sip->registerstatus != 2) {
3295 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3296 if (sip->registrar.retries > 3) {
3297 sip->gc->wants_to_die = TRUE;
3298 purple_connection_error(sip->gc, _("Wrong Password"));
3299 return TRUE;
3301 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3302 tmp = sipmsg_find_auth_header(msg, "NTLM");
3303 } else {
3304 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3306 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3307 fill_auth(sip, tmp, &sip->registrar);
3308 sip->registerstatus = 2;
3309 if (sip->account->disconnecting) {
3310 do_register_exp(sip, 0);
3311 } else {
3312 do_register(sip);
3315 break;
3316 case 403:
3318 gchar *warning = sipmsg_find_header(msg, "Warning");
3319 if (warning != NULL) {
3320 /* Example header:
3321 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3323 gchar **tmp = g_strsplit(warning, "\"", 0);
3324 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3325 g_strfreev(tmp);
3326 } else {
3327 warning = g_strdup(_("You have been rejected by the server"));
3330 sip->gc->wants_to_die = TRUE;
3331 purple_connection_error(sip->gc, warning);
3332 g_free(warning);
3333 return TRUE;
3335 break;
3336 case 404:
3338 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3339 if (warning != NULL) {
3340 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3341 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3342 g_free(reason);
3343 } else {
3344 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3347 sip->gc->wants_to_die = TRUE;
3348 purple_connection_error(sip->gc, warning);
3349 g_free(warning);
3350 return TRUE;
3352 break;
3353 case 503:
3355 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3356 if (warning != NULL) {
3357 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3358 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3359 g_free(reason);
3360 } else {
3361 warning = g_strdup(_("Service unavailable: no reason given"));
3364 sip->gc->wants_to_die = TRUE;
3365 purple_connection_error(sip->gc, warning);
3366 g_free(warning);
3367 return TRUE;
3369 break;
3371 return TRUE;
3374 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3376 const char *uri;
3377 xmlnode *xn_categories;
3378 xmlnode *xn_category;
3379 xmlnode *xn_node;
3380 const char *activity = NULL;
3382 xn_categories = xmlnode_from_str(data, len);
3383 uri = xmlnode_get_attrib(xn_categories, "uri");
3385 for (xn_category = xmlnode_get_child(xn_categories, "category");
3386 xn_category ;
3387 xn_category = xmlnode_get_next_twin(xn_category) )
3389 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
3391 if (!strcmp(attrVar, "note"))
3393 char *note;
3394 struct sipe_buddy *sbuddy;
3395 xn_node = xmlnode_get_child(xn_category, "note");
3396 if (!xn_node) continue;
3397 xn_node = xmlnode_get_child(xn_node, "body");
3398 if (!xn_node) continue;
3400 note = xmlnode_get_data(xn_node);
3402 if(uri){
3403 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3405 if (sbuddy && note)
3407 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note);
3408 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3409 sbuddy->annotation = g_strdup(note);
3411 if(note)
3412 g_free(note);
3414 else if(!strcmp(attrVar, "state"))
3416 char *data;
3417 int avail;
3418 xn_node = xmlnode_get_child(xn_category, "state");
3419 if (!xn_node) continue;
3420 xn_node = xmlnode_get_child(xn_node, "availability");
3421 if (!xn_node) continue;
3423 data = xmlnode_get_data(xn_node);
3424 avail = atoi(data);
3425 g_free(data);
3427 if (avail < 3000)
3428 activity = SIPE_STATUS_ID_UNKNOWN;
3429 else if (avail < 4500)
3430 activity = SIPE_STATUS_ID_AVAILABLE;
3431 else if (avail < 6000)
3432 activity = SIPE_STATUS_ID_BRB;
3433 else if (avail < 7500)
3434 activity = SIPE_STATUS_ID_ONPHONE;
3435 else if (avail < 9000)
3436 activity = SIPE_STATUS_ID_BUSY;
3437 else if (avail < 12000)
3438 activity = SIPE_STATUS_ID_DND;
3439 else if (avail < 18000)
3440 activity = SIPE_STATUS_ID_AWAY;
3441 else
3442 activity = SIPE_STATUS_ID_OFFLINE;
3445 if(activity) {
3446 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
3447 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
3450 xmlnode_free(xn_categories);
3453 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
3455 const char *uri,*state;
3456 xmlnode *xn_list;
3457 xmlnode *xn_resource;
3458 xmlnode *xn_instance;
3460 xn_list = xmlnode_from_str(data, len);
3462 for (xn_resource = xmlnode_get_child(xn_list, "resource");
3463 xn_resource;
3464 xn_resource = xmlnode_get_next_twin(xn_resource) )
3466 uri = xmlnode_get_attrib(xn_resource, "uri");
3467 xn_instance = xmlnode_get_child(xn_resource, "instance");
3468 if (!xn_instance) return;
3470 state = xmlnode_get_attrib(xn_instance, "state");
3471 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri,state);
3472 if(strstr(state,"resubscribe")){
3473 sipe_subscribe_presence_single(sip, uri);
3478 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
3480 const gchar *uri;
3481 gchar *getbasic;
3482 gchar *activity = NULL;
3483 xmlnode *pidf;
3484 xmlnode *basicstatus = NULL, *tuple, *status;
3485 gboolean isonline = FALSE;
3486 xmlnode *display_name_node;
3488 pidf = xmlnode_from_str(data, len);
3489 if (!pidf) {
3490 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
3491 return;
3494 uri = xmlnode_get_attrib(pidf, "entity");
3496 if ((tuple = xmlnode_get_child(pidf, "tuple")))
3498 if ((status = xmlnode_get_child(tuple, "status"))) {
3499 basicstatus = xmlnode_get_child(status, "basic");
3503 if (!basicstatus) {
3504 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3505 xmlnode_free(pidf);
3506 return;
3509 getbasic = xmlnode_get_data(basicstatus);
3510 if (!getbasic) {
3511 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3512 xmlnode_free(pidf);
3513 return;
3516 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
3517 if (strstr(getbasic, "open")) {
3518 isonline = TRUE;
3520 g_free(getbasic);
3522 display_name_node = xmlnode_get_child(pidf, "display-name");
3523 // updating display name if alias was just URI
3524 if (display_name_node) {
3525 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3526 GSList *entry = buddies;
3527 PurpleBuddy *p_buddy;
3528 char * display_name = xmlnode_get_data(display_name_node);
3530 while (entry) {
3531 const char *server_alias;
3532 char *alias;
3534 p_buddy = entry->data;
3536 alias = (char *)purple_buddy_get_alias(p_buddy);
3537 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
3538 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
3539 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3540 purple_blist_alias_buddy(p_buddy, display_name);
3542 g_free(alias);
3544 server_alias = purple_buddy_get_server_alias(p_buddy);
3545 if (display_name &&
3546 ( (server_alias && strcmp(display_name, server_alias))
3547 || !server_alias || strlen(server_alias) == 0 )
3549 purple_blist_server_alias_buddy(p_buddy, display_name);
3552 entry = entry->next;
3554 g_free(display_name);
3557 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
3558 if ((status = xmlnode_get_child(tuple, "status"))) {
3559 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
3560 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
3561 activity = xmlnode_get_data(basicstatus);
3562 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
3568 if (isonline) {
3569 const gchar * status_id = NULL;
3570 if (activity) {
3571 if (strstr(activity, "busy")) {
3572 status_id = SIPE_STATUS_ID_BUSY;
3573 } else if (strstr(activity, "away")) {
3574 status_id = SIPE_STATUS_ID_AWAY;
3578 if (!status_id) {
3579 status_id = SIPE_STATUS_ID_AVAILABLE;
3582 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
3583 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
3584 } else {
3585 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
3588 g_free(activity);
3589 xmlnode_free(pidf);
3592 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
3594 const char *availability;
3595 const char *activity;
3596 const char *display_name = NULL;
3597 const char *activity_name = NULL;
3598 const char *name;
3599 char *uri;
3600 int avl;
3601 int act;
3602 struct sipe_buddy *sbuddy;
3604 xmlnode *xn_presentity = xmlnode_from_str(data, len);
3606 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
3607 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
3608 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
3609 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
3610 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
3611 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
3612 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
3613 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
3615 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
3616 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
3617 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
3618 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
3619 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
3620 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
3622 name = xmlnode_get_attrib(xn_presentity, "uri");
3623 uri = g_strdup_printf("sip:%s", name);
3624 availability = xmlnode_get_attrib(xn_availability, "aggregate");
3625 activity = xmlnode_get_attrib(xn_activity, "aggregate");
3627 // updating display name if alias was just URI
3628 if (xn_display_name) {
3629 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
3630 GSList *entry = buddies;
3631 PurpleBuddy *p_buddy;
3632 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
3634 while (entry) {
3635 const char *email_str, *server_alias;
3637 p_buddy = entry->data;
3639 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
3640 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
3641 purple_blist_alias_buddy(p_buddy, display_name);
3644 server_alias = purple_buddy_get_server_alias(p_buddy);
3645 if (display_name &&
3646 ( (server_alias && strcmp(display_name, server_alias))
3647 || !server_alias || strlen(server_alias) == 0 )
3649 purple_blist_server_alias_buddy(p_buddy, display_name);
3652 if (email) {
3653 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
3654 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
3655 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
3659 entry = entry->next;
3663 avl = atoi(availability);
3664 act = atoi(activity);
3666 if(sip->msrtc_event_categories){
3667 if (act == 100 && avl == 0)
3668 activity_name = SIPE_STATUS_ID_OFFLINE;
3669 else if (act == 100 && avl == 300)
3670 activity_name = SIPE_STATUS_ID_AWAY;
3671 else if (act == 300 && avl == 300)
3672 activity_name = SIPE_STATUS_ID_BRB;
3673 else if (act == 400 && avl == 300)
3674 activity_name = SIPE_STATUS_ID_AVAILABLE;
3675 else if (act == 500 && act == 300)
3676 activity_name = SIPE_STATUS_ID_ONPHONE;
3677 else if (act == 600 && avl == 300)
3678 activity_name = SIPE_STATUS_ID_BUSY;
3679 else if (act == 0 && avl == 0){ //MSRTC elements are zero
3680 if(avail){ //Check for LegacyInterop elements
3681 avl = atoi(avail);
3682 if(avl == 18500)
3683 activity_name = SIPE_STATUS_ID_OFFLINE;
3684 else if (avl == 3500)
3685 activity_name = SIPE_STATUS_ID_AVAILABLE;
3686 else if (avl == 15500)
3687 activity_name = SIPE_STATUS_ID_AWAY;
3688 else if (avl == 6500)
3689 activity_name = SIPE_STATUS_ID_BUSY;
3690 else if (avl == 12500)
3691 activity_name = SIPE_STATUS_ID_BRB;
3696 if(activity_name == NULL){
3697 if (act <= 100)
3698 activity_name = SIPE_STATUS_ID_AWAY;
3699 else if (act <= 150)
3700 activity_name = SIPE_STATUS_ID_LUNCH;
3701 else if (act <= 300)
3702 activity_name = SIPE_STATUS_ID_BRB;
3703 else if (act <= 400)
3704 activity_name = SIPE_STATUS_ID_AVAILABLE;
3705 else if (act <= 500)
3706 activity_name = SIPE_STATUS_ID_ONPHONE;
3707 else if (act <= 600)
3708 activity_name = SIPE_STATUS_ID_BUSY;
3709 else
3710 activity_name = SIPE_STATUS_ID_AVAILABLE;
3712 if (avl == 0)
3713 activity_name = SIPE_STATUS_ID_OFFLINE;
3716 sbuddy = g_hash_table_lookup(sip->buddies, uri);
3717 if (sbuddy)
3719 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
3720 sbuddy->annotation = NULL;
3721 if (note) { sbuddy->annotation = g_strdup(note); }
3723 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
3724 sbuddy->device_name = NULL;
3725 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
3728 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
3729 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
3730 g_free(note);
3731 xmlnode_free(xn_presentity);
3732 g_free(uri);
3735 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
3737 char *ctype = sipmsg_find_header(msg, "Content-Type");
3739 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
3741 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
3742 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
3744 const char *content = msg->body;
3745 unsigned length = msg->bodylen;
3746 PurpleMimeDocument *mime = NULL;
3748 if (strstr(ctype, "multipart"))
3750 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
3751 const char *content_type;
3752 GList* parts;
3753 mime = purple_mime_document_parse(doc);
3754 parts = purple_mime_document_get_parts(mime);
3755 while(parts) {
3756 content = purple_mime_part_get_data(parts->data);
3757 length = purple_mime_part_get_length(parts->data);
3758 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
3759 if(content_type && strstr(content_type,"application/rlmi+xml"))
3761 process_incoming_notify_rlmi_resub(sip, content, length);
3763 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
3765 process_incoming_notify_msrtc(sip, content, length);
3767 else
3769 process_incoming_notify_rlmi(sip, content, length);
3771 parts = parts->next;
3773 g_free(doc);
3775 if (mime)
3777 purple_mime_document_free(mime);
3780 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3782 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
3784 else if(strstr(ctype, "application/rlmi+xml"))
3786 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
3789 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3791 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
3793 else
3795 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
3800 * Dispatcher for all incoming subscription information
3801 * whether it comes from NOTIFY, BENOTIFY requests or
3802 * piggy-backed to subscription's OK responce.
3804 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3805 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3807 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
3809 gchar *event = sipmsg_find_header(msg, "Event");
3810 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3811 int timeout = 0;
3813 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
3814 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
3816 if (!subscription_state || strstr(subscription_state, "active"))
3818 if (event && !g_ascii_strcasecmp(event, "presence"))
3820 sipe_process_presence(sip, msg);
3822 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
3824 sipe_process_roaming_contacts(sip, msg, NULL);
3826 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
3828 sipe_process_roaming_self(sip, msg);
3830 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
3832 sipe_process_roaming_acl(sip, msg);
3834 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
3836 sipe_process_presence_wpending(sip, msg);
3838 else
3840 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
3844 //The server sends a (BE)NOTIFY with the status 'terminated'
3845 if(request && subscription_state && strstr(subscription_state, "terminated") ) {
3846 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3847 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
3848 g_free(from);
3851 //For OCS2007/LCS2005 we need to resubscribe the contacts before the expires time in seconds comes to 0
3852 //Can be benotify, notify or 200 0k
3853 if (event && subscription_state && strstr(subscription_state, "active") && !g_ascii_strcasecmp(event, "presence"))//Notify or Benotify
3855 const gchar *expires_contact;
3856 expires_contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "subscription-state"), "expires=", ";", NULL);
3857 timeout = expires_contact ? strtol(expires_contact, NULL, 10) : 0;
3858 if (timeout){
3859 purple_debug_info("sipe", "process_incoming_notify: expires_contact:%d\n\n", timeout);
3860 timeout = timeout - 300;
3861 if (timeout <= 0){ // Only when expires - 300 <= 0 secs
3862 purple_debug_info("sipe", "process_incoming_notify: expires_contact needs re-subscribe:%d\n\n", timeout);
3863 if (g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp)){
3864 gchar *who = parse_from(sipmsg_find_header(msg, "From"));
3865 gchar *action_name = g_strdup_printf("<%s><%s>", "presence", who);
3866 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_batched, sip, who);
3867 g_free(action_name);
3868 g_free(who);
3873 else if (!request && !benotify && event && subscription_state && strstr(subscription_state, "active") && !g_ascii_strcasecmp(event, "presence.wpending")) //Ok 200
3875 const gchar *expires_header;
3876 expires_header = sipmsg_find_header(msg, "Expires");
3877 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3878 if(timeout){
3879 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", timeout);
3880 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
3881 if (g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3883 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3884 sipe_schedule_action(action_name, timeout, (Action) sipe_subscribe_presence_wpending, sip, NULL);
3885 g_free(action_name);
3890 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
3892 sipe_process_registration_notify(sip, msg);
3895 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3896 if (request && !benotify)
3898 sipmsg_remove_header(msg, "Expires");
3899 sipmsg_remove_header(msg, "subscription-state");
3900 sipmsg_remove_header(msg, "Event");
3901 sipmsg_remove_header(msg, "Require");
3902 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3907 * unused. Needed?
3909 static gchar* gen_xpidf(struct sipe_account_data *sip)
3911 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3912 "<presence>\r\n"
3913 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3914 "<display name=\"sip:%s\"/>\r\n"
3915 "<atom id=\"1234\">\r\n"
3916 "<address uri=\"sip:%s\">\r\n"
3917 "<status status=\"%s\"/>\r\n"
3918 "</address>\r\n"
3919 "</atom>\r\n"
3920 "</presence>\r\n",
3921 sip->username,
3922 sip->username,
3923 sip->username,
3924 sip->status);
3925 return doc;
3930 static gchar* gen_pidf(struct sipe_account_data *sip)
3932 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3933 "<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"
3934 "<tuple id=\"0\">\r\n"
3935 "<status>\r\n"
3936 "<basic>open</basic>\r\n"
3937 "<ep:activities>\r\n"
3938 " <ep:activity>%s</ep:activity>\r\n"
3939 "</ep:activities>"
3940 "</status>\r\n"
3941 "</tuple>\r\n"
3942 "<ci:display-name>%s</ci:display-name>\r\n"
3943 "</presence>",
3944 sip->username,
3945 sip->status,
3946 sip->username);
3947 return doc;
3951 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
3953 int availability = 300; // online
3954 int activity = 400; // Available
3955 gchar *name;
3956 gchar *body;
3957 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
3958 activity = 100;
3959 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
3960 activity = 150;
3961 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
3962 activity = 300;
3963 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
3964 activity = 400;
3965 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
3966 activity = 500;
3967 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
3968 activity = 600;
3969 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
3970 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
3971 availability = 0; // offline
3972 activity = 100;
3973 } else {
3974 activity = 400; // available
3977 name = g_strdup_printf("sip: sip:%s", sip->username);
3978 //@TODO: send user data - state; add hostname in upper case
3979 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
3980 send_soap_request_with_cb(sip, body, NULL , NULL);
3981 g_free(name);
3982 g_free(body);
3985 static gboolean
3986 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3988 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3989 if (msg->response == 200) {
3990 sip->status_version = 0;
3991 send_presence_status(sip);
3993 return TRUE;
3996 static gboolean
3997 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3999 if (msg->response == 409) {
4000 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4001 // TODO need to parse the version #'s?
4002 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4003 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4004 gchar *tmp;
4005 gchar *hdr;
4007 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4009 tmp = get_contact(sip);
4010 hdr = g_strdup_printf("Contact: %s\r\n"
4011 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4013 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4015 g_free(tmp);
4016 g_free(hdr);
4017 g_free(uri);
4018 g_free(doc);
4020 return TRUE;
4023 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4025 int code;
4026 gchar *uri;
4027 gchar *doc;
4028 gchar *tmp;
4029 gchar *hdr;
4030 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4031 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4032 code = 12000;
4033 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4034 code = 9000;
4035 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4036 code = 7500;
4037 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4038 code = 6000;
4039 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4040 code = 4500;
4041 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4042 code = 3000;
4043 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4044 code = 0;
4045 } else {
4046 // Offline or invisible
4047 code = 18000;
4050 uri = g_strdup_printf("sip:%s", sip->username);
4051 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4052 sip->status_version, code,
4053 sip->status_version, code,
4054 sip->status_version, note ? note : "",
4055 sip->status_version, note ? note : "",
4056 sip->status_version, note ? note : ""
4058 sip->status_version++;
4060 tmp = get_contact(sip);
4061 hdr = g_strdup_printf("Contact: %s\r\n"
4062 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4064 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4066 g_free(tmp);
4067 g_free(hdr);
4068 g_free(uri);
4069 g_free(doc);
4072 static void send_presence_status(struct sipe_account_data *sip)
4074 PurpleStatus * status = purple_account_get_active_status(sip->account);
4075 const gchar *note;
4076 if (!status) return;
4078 note = purple_status_get_attr_string(status, "message");
4080 if(sip->msrtc_event_categories){
4081 send_presence_category_publish(sip, note);
4082 } else {
4083 send_presence_soap(sip, note);
4087 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4089 gboolean found = FALSE;
4090 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4091 if (msg->response == 0) { /* request */
4092 if (!strcmp(msg->method, "MESSAGE")) {
4093 process_incoming_message(sip, msg);
4094 found = TRUE;
4095 } else if (!strcmp(msg->method, "NOTIFY")) {
4096 purple_debug_info("sipe","send->process_incoming_notify\n");
4097 process_incoming_notify(sip, msg, TRUE, FALSE);
4098 found = TRUE;
4099 } else if (!strcmp(msg->method, "BENOTIFY")) {
4100 purple_debug_info("sipe","send->process_incoming_benotify\n");
4101 process_incoming_notify(sip, msg, TRUE, TRUE);
4102 found = TRUE;
4103 } else if (!strcmp(msg->method, "INVITE")) {
4104 process_incoming_invite(sip, msg);
4105 found = TRUE;
4106 } else if (!strcmp(msg->method, "OPTIONS")) {
4107 process_incoming_options(sip, msg);
4108 found = TRUE;
4109 } else if (!strcmp(msg->method, "INFO")) {
4110 // TODO needs work
4111 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4112 if (from) {
4113 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
4115 g_free(from);
4116 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4117 found = TRUE;
4118 } else if (!strcmp(msg->method, "ACK")) {
4119 // ACK's don't need any response
4120 found = TRUE;
4121 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4122 // LCS 2005 sends us these - just respond 200 OK
4123 found = TRUE;
4124 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4125 } else if (!strcmp(msg->method, "BYE")) {
4126 struct sip_im_session *session;
4127 gchar *from;
4128 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4130 from = parse_from(sipmsg_find_header(msg, "From"));
4131 session = find_im_session (sip, from);
4132 g_free(from);
4134 if (session) {
4135 // TODO Let the user know the other user left the conversation?
4136 im_session_destroy(sip, session);
4139 found = TRUE;
4140 } else {
4141 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4143 } else { /* response */
4144 struct transaction *trans = transactions_find(sip, msg);
4145 if (trans) {
4146 if (msg->response == 407) {
4147 gchar *resend, *auth, *ptmp;
4149 if (sip->proxy.retries > 30) return;
4150 sip->proxy.retries++;
4151 /* do proxy authentication */
4153 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4155 fill_auth(sip, ptmp, &sip->proxy);
4156 auth = auth_header(sip, &sip->proxy, trans->msg);
4157 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4158 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4159 g_free(auth);
4160 resend = sipmsg_to_string(trans->msg);
4161 /* resend request */
4162 sendout_pkt(sip->gc, resend);
4163 g_free(resend);
4164 } else {
4165 if (msg->response == 100 || msg->response == 180) {
4166 /* ignore provisional response */
4167 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4168 } else {
4169 sip->proxy.retries = 0;
4170 if (!strcmp(trans->msg->method, "REGISTER")) {
4171 if (msg->response == 401)
4173 sip->registrar.retries++;
4174 sip->registrar.expires = 0;
4176 else
4178 sip->registrar.retries = 0;
4180 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4181 } else {
4182 if (msg->response == 401) {
4183 gchar *resend, *auth, *ptmp;
4185 if (sip->registrar.retries > 4) return;
4186 sip->registrar.retries++;
4188 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4189 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4190 } else {
4191 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4194 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4196 fill_auth(sip, ptmp, &sip->registrar);
4197 auth = auth_header(sip, &sip->registrar, trans->msg);
4198 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4199 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
4201 //sipmsg_remove_header(trans->msg, "Authorization");
4202 //sipmsg_add_header(trans->msg, "Authorization", auth);
4203 g_free(auth);
4204 resend = sipmsg_to_string(trans->msg);
4205 /* resend request */
4206 sendout_pkt(sip->gc, resend);
4207 g_free(resend);
4211 if (trans->callback) {
4212 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4213 /* call the callback to process response*/
4214 (trans->callback)(sip, msg, trans);
4216 /* Not sure if this is needed or what needs to be done
4217 but transactions seem to be removed prematurely so
4218 this only removes them if the response is 200 OK */
4219 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4220 /*Has a bug and it's unneccesary*/
4221 /*transactions_remove(sip, trans);*/
4225 found = TRUE;
4226 } else {
4227 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4230 if (!found) {
4231 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4235 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4237 char *cur;
4238 char *dummy;
4239 struct sipmsg *msg;
4240 int restlen;
4241 cur = conn->inbuf;
4243 /* according to the RFC remove CRLF at the beginning */
4244 while (*cur == '\r' || *cur == '\n') {
4245 cur++;
4247 if (cur != conn->inbuf) {
4248 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4249 conn->inbufused = strlen(conn->inbuf);
4252 /* Received a full Header? */
4253 sip->processing_input = TRUE;
4254 while (sip->processing_input &&
4255 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4256 time_t currtime = time(NULL);
4257 cur += 2;
4258 cur[0] = '\0';
4259 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4260 msg = sipmsg_parse_header(conn->inbuf);
4261 cur[0] = '\r';
4262 cur += 2;
4263 restlen = conn->inbufused - (cur - conn->inbuf);
4264 if (restlen >= msg->bodylen) {
4265 dummy = g_malloc(msg->bodylen + 1);
4266 memcpy(dummy, cur, msg->bodylen);
4267 dummy[msg->bodylen] = '\0';
4268 msg->body = dummy;
4269 cur += msg->bodylen;
4270 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4271 conn->inbufused = strlen(conn->inbuf);
4272 } else {
4273 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4274 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4275 sipmsg_free(msg);
4276 return;
4279 /*if (msg->body) {
4280 purple_debug_info("sipe", "body:\n%s", msg->body);
4283 // Verify the signature before processing it
4284 if (sip->registrar.ntlm_key) {
4285 struct sipmsg_breakdown msgbd;
4286 gchar *signature_input_str;
4287 gchar *signature = NULL;
4288 gchar *rspauth;
4289 msgbd.msg = msg;
4290 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
4291 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
4292 if (signature_input_str != NULL) {
4293 signature = purple_ntlm_sipe_signature_make (signature_input_str, sip->registrar.ntlm_key);
4295 g_free(signature_input_str);
4297 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
4299 if (signature != NULL) {
4300 if (rspauth != NULL) {
4301 if (purple_ntlm_verify_signature (signature, rspauth)) {
4302 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
4303 process_input_message(sip, msg);
4304 } else {
4305 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth, signature);
4306 purple_connection_error(sip->gc, _("Invalid message signature received"));
4307 sip->gc->wants_to_die = TRUE;
4309 } else if (msg->response == 401) {
4310 purple_connection_error(sip->gc, _("Wrong Password"));
4311 sip->gc->wants_to_die = TRUE;
4313 g_free(signature);
4316 g_free(rspauth);
4317 sipmsg_breakdown_free(&msgbd);
4318 } else {
4319 process_input_message(sip, msg);
4322 sipmsg_free(msg);
4326 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
4328 PurpleConnection *gc = data;
4329 struct sipe_account_data *sip = gc->proto_data;
4330 struct sipmsg *msg;
4331 int len;
4332 time_t currtime;
4334 static char buffer[65536];
4335 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
4336 buffer[len] = '\0';
4337 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
4338 msg = sipmsg_parse_msg(buffer);
4339 if (msg) process_input_message(sip, msg);
4343 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
4345 struct sipe_account_data *sip = gc->proto_data;
4346 PurpleSslConnection *gsc = sip->gsc;
4348 purple_debug_error("sipe", "%s",debug);
4349 purple_connection_error(gc, msg);
4351 /* Invalidate this connection. Next send will open a new one */
4352 if (gsc) {
4353 connection_remove(sip, gsc->fd);
4354 purple_ssl_close(gsc);
4356 sip->gsc = NULL;
4357 sip->fd = -1;
4360 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4362 PurpleConnection *gc = data;
4363 struct sipe_account_data *sip;
4364 struct sip_connection *conn;
4365 int readlen, len;
4366 gboolean firstread = TRUE;
4368 /* NOTE: This check *IS* necessary */
4369 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
4370 purple_ssl_close(gsc);
4371 return;
4374 sip = gc->proto_data;
4375 conn = connection_find(sip, gsc->fd);
4376 if (conn == NULL) {
4377 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4378 gc->wants_to_die = TRUE;
4379 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
4380 return;
4383 /* Read all available data from the SSL connection */
4384 do {
4385 /* Increase input buffer size as needed */
4386 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4387 conn->inbuflen += SIMPLE_BUF_INC;
4388 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4389 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
4392 /* Try to read as much as there is space left in the buffer */
4393 readlen = conn->inbuflen - conn->inbufused - 1;
4394 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
4396 if (len < 0 && errno == EAGAIN) {
4397 /* Try again later */
4398 return;
4399 } else if (len < 0) {
4400 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
4401 return;
4402 } else if (firstread && (len == 0)) {
4403 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
4404 return;
4407 conn->inbufused += len;
4408 firstread = FALSE;
4410 /* Equivalence indicates that there is possibly more data to read */
4411 } while (len == readlen);
4413 conn->inbuf[conn->inbufused] = '\0';
4414 process_input(sip, conn);
4418 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
4420 PurpleConnection *gc = data;
4421 struct sipe_account_data *sip = gc->proto_data;
4422 int len;
4423 struct sip_connection *conn = connection_find(sip, source);
4424 if (!conn) {
4425 purple_debug_error("sipe", "Connection not found!\n");
4426 return;
4429 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
4430 conn->inbuflen += SIMPLE_BUF_INC;
4431 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
4434 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
4436 if (len < 0 && errno == EAGAIN)
4437 return;
4438 else if (len <= 0) {
4439 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4440 connection_remove(sip, source);
4441 if (sip->fd == source) sip->fd = -1;
4442 return;
4445 conn->inbufused += len;
4446 conn->inbuf[conn->inbufused] = '\0';
4448 process_input(sip, conn);
4451 /* Callback for new connections on incoming TCP port */
4452 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
4454 PurpleConnection *gc = data;
4455 struct sipe_account_data *sip = gc->proto_data;
4456 struct sip_connection *conn;
4458 int newfd = accept(source, NULL, NULL);
4460 conn = connection_create(sip, newfd);
4462 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4465 static void login_cb(gpointer data, gint source, const gchar *error_message)
4467 PurpleConnection *gc = data;
4468 struct sipe_account_data *sip;
4469 struct sip_connection *conn;
4471 if (!PURPLE_CONNECTION_IS_VALID(gc))
4473 if (source >= 0)
4474 close(source);
4475 return;
4478 if (source < 0) {
4479 purple_connection_error(gc, _("Could not connect"));
4480 return;
4483 sip = gc->proto_data;
4484 sip->fd = source;
4485 sip->last_keepalive = time(NULL);
4487 conn = connection_create(sip, source);
4489 do_register(sip);
4491 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
4494 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
4496 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
4497 if (sip == NULL) return;
4499 do_register(sip);
4502 static guint sipe_ht_hash_nick(const char *nick)
4504 char *lc = g_utf8_strdown(nick, -1);
4505 guint bucket = g_str_hash(lc);
4506 g_free(lc);
4508 return bucket;
4511 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4513 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
4516 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
4518 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4520 sip->listen_data = NULL;
4522 if (listenfd == -1) {
4523 purple_connection_error(sip->gc, _("Could not create listen socket"));
4524 return;
4527 sip->fd = listenfd;
4529 sip->listenport = purple_network_get_port_from_fd(sip->fd);
4530 sip->listenfd = sip->fd;
4532 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
4534 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
4535 do_register(sip);
4538 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
4540 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4541 int addr_size;
4543 sip->query_data = NULL;
4545 if (!hosts || !hosts->data) {
4546 purple_connection_error(sip->gc, _("Couldn't resolve host"));
4547 return;
4550 addr_size = GPOINTER_TO_INT(hosts->data);
4551 hosts = g_slist_remove(hosts, hosts->data);
4552 memcpy(&(sip->serveraddr), hosts->data, addr_size);
4553 g_free(hosts->data);
4554 hosts = g_slist_remove(hosts, hosts->data);
4555 while (hosts) {
4556 hosts = g_slist_remove(hosts, hosts->data);
4557 g_free(hosts->data);
4558 hosts = g_slist_remove(hosts, hosts->data);
4561 /* create socket for incoming connections */
4562 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
4563 sipe_udp_host_resolved_listen_cb, sip);
4564 if (sip->listen_data == NULL) {
4565 purple_connection_error(sip->gc, _("Could not create listen socket"));
4566 return;
4570 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
4571 gpointer data)
4573 PurpleConnection *gc = data;
4574 struct sipe_account_data *sip;
4576 /* If the connection is already disconnected, we don't need to do anything else */
4577 if (!PURPLE_CONNECTION_IS_VALID(gc))
4578 return;
4580 sip = gc->proto_data;
4581 sip->fd = -1;
4582 sip->gsc = NULL;
4584 switch(error) {
4585 case PURPLE_SSL_CONNECT_FAILED:
4586 purple_connection_error(gc, _("Connection Failed"));
4587 break;
4588 case PURPLE_SSL_HANDSHAKE_FAILED:
4589 purple_connection_error(gc, _("SSL Handshake Failed"));
4590 break;
4591 case PURPLE_SSL_CERTIFICATE_INVALID:
4592 purple_connection_error(gc, _("SSL Certificate Invalid"));
4593 break;
4597 static void
4598 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
4600 struct sipe_account_data *sip = (struct sipe_account_data*) data;
4601 PurpleProxyConnectData *connect_data;
4603 sip->listen_data = NULL;
4605 sip->listenfd = listenfd;
4606 if (sip->listenfd == -1) {
4607 purple_connection_error(sip->gc, _("Could not create listen socket"));
4608 return;
4611 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
4612 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4613 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4614 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
4615 sipe_newconn_cb, sip->gc);
4616 purple_debug_info("sipe", "connecting to %s port %d\n",
4617 sip->realhostname, sip->realport);
4618 /* open tcp connection to the server */
4619 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
4620 sip->realport, login_cb, sip->gc);
4622 if (connect_data == NULL) {
4623 purple_connection_error(sip->gc, _("Couldn't create socket"));
4628 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
4630 PurpleAccount *account = sip->account;
4631 PurpleConnection *gc = sip->gc;
4633 if (purple_account_get_bool(account, "useport", FALSE)) {
4634 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
4635 port = purple_account_get_int(account, "port", 0);
4636 } else {
4637 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
4640 sip->realhostname = hostname;
4641 sip->realport = port;
4643 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
4644 hostname, port);
4646 /* TODO: is there a good default grow size? */
4647 if (sip->transport != SIPE_TRANSPORT_UDP)
4648 sip->txbuf = purple_circ_buffer_new(0);
4650 if (sip->transport == SIPE_TRANSPORT_TLS) {
4651 /* SSL case */
4652 if (!purple_ssl_is_supported()) {
4653 gc->wants_to_die = TRUE;
4654 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4655 return;
4658 purple_debug_info("sipe", "using SSL\n");
4660 sip->gsc = purple_ssl_connect(account, hostname, port,
4661 login_cb_ssl, sipe_ssl_connect_failure, gc);
4662 if (sip->gsc == NULL) {
4663 purple_connection_error(gc, _("Could not create SSL context"));
4664 return;
4666 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
4667 /* UDP case */
4668 purple_debug_info("sipe", "using UDP\n");
4670 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
4671 if (sip->query_data == NULL) {
4672 purple_connection_error(gc, _("Could not resolve hostname"));
4674 } else {
4675 /* TCP case */
4676 purple_debug_info("sipe", "using TCP\n");
4677 /* create socket for incoming connections */
4678 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
4679 sipe_tcp_connect_listen_cb, sip);
4680 if (sip->listen_data == NULL) {
4681 purple_connection_error(gc, _("Could not create listen socket"));
4682 return;
4687 /* Service list for autodection */
4688 static const struct sipe_service_data service_autodetect[] = {
4689 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4690 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4691 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4692 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4693 { NULL, NULL, 0 }
4696 /* Service list for SSL/TLS */
4697 static const struct sipe_service_data service_tls[] = {
4698 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
4699 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
4700 { NULL, NULL, 0 }
4703 /* Service list for TCP */
4704 static const struct sipe_service_data service_tcp[] = {
4705 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
4706 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
4707 { NULL, NULL, 0 }
4710 /* Service list for UDP */
4711 static const struct sipe_service_data service_udp[] = {
4712 { "sip", "udp", SIPE_TRANSPORT_UDP },
4713 { NULL, NULL, 0 }
4716 static void srvresolved(PurpleSrvResponse *, int, gpointer);
4717 static void resolve_next_service(struct sipe_account_data *sip,
4718 const struct sipe_service_data *start)
4720 if (start) {
4721 sip->service_data = start;
4722 } else {
4723 sip->service_data++;
4724 if (sip->service_data->service == NULL) {
4725 gchar *hostname;
4726 /* Try connecting to the SIP hostname directly */
4727 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
4728 if (sip->auto_transport) {
4729 // If SSL is supported, default to using it; OCS servers aren't configured
4730 // by default to accept TCP
4731 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4732 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
4733 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
4736 hostname = g_strdup(sip->sipdomain);
4737 create_connection(sip, hostname, 0);
4738 return;
4742 /* Try to resolve next service */
4743 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
4744 sip->service_data->transport,
4745 sip->sipdomain,
4746 srvresolved, sip);
4749 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
4751 struct sipe_account_data *sip = data;
4753 sip->srv_query_data = NULL;
4755 /* find the host to connect to */
4756 if (results) {
4757 gchar *hostname = g_strdup(resp->hostname);
4758 int port = resp->port;
4759 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4760 hostname, port);
4761 g_free(resp);
4763 sip->transport = sip->service_data->type;
4765 create_connection(sip, hostname, port);
4766 } else {
4767 resolve_next_service(sip, NULL);
4771 static void sipe_login(PurpleAccount *account)
4773 PurpleConnection *gc;
4774 struct sipe_account_data *sip;
4775 gchar **signinname_login, **userserver, **domain_user;
4776 const char *transport;
4778 const char *username = purple_account_get_username(account);
4779 gc = purple_account_get_connection(account);
4781 if (strpbrk(username, "\t\v\r\n") != NULL) {
4782 gc->wants_to_die = TRUE;
4783 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
4784 return;
4787 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
4788 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
4789 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
4790 sip->gc = gc;
4791 sip->account = account;
4792 sip->reregister_set = FALSE;
4793 sip->reauthenticate_set = FALSE;
4794 sip->subscribed = FALSE;
4795 sip->subscribed_buddies = FALSE;
4797 signinname_login = g_strsplit(username, ",", 2);
4799 userserver = g_strsplit(signinname_login[0], "@", 2);
4800 purple_connection_set_display_name(gc, userserver[0]);
4801 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
4802 sip->sipdomain = g_strdup(userserver[1]);
4804 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
4805 gc->wants_to_die = TRUE;
4806 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
4807 return;
4810 domain_user = g_strsplit(signinname_login[1], "\\", 2);
4811 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
4812 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
4814 sip->password = g_strdup(purple_connection_get_password(gc));
4816 g_strfreev(userserver);
4817 g_strfreev(domain_user);
4818 g_strfreev(signinname_login);
4820 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4822 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
4824 /* TODO: Set the status correctly. */
4825 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4827 transport = purple_account_get_string(account, "transport", "auto");
4828 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
4829 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
4830 SIPE_TRANSPORT_UDP;
4832 if (purple_account_get_bool(account, "useproxy", FALSE)) {
4833 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
4834 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
4835 } else if (strcmp(transport, "auto") == 0) {
4836 sip->auto_transport = TRUE;
4837 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
4838 } else if (strcmp(transport, "tls") == 0) {
4839 resolve_next_service(sip, service_tls);
4840 } else if (strcmp(transport, "tcp") == 0) {
4841 resolve_next_service(sip, service_tcp);
4842 } else {
4843 resolve_next_service(sip, service_udp);
4847 static void sipe_connection_cleanup(struct sipe_account_data *sip)
4849 connection_free_all(sip);
4851 g_free(sip->epid);
4852 sip->epid = NULL;
4854 if (sip->query_data != NULL)
4855 purple_dnsquery_destroy(sip->query_data);
4856 sip->query_data = NULL;
4858 if (sip->srv_query_data != NULL)
4859 purple_srv_cancel(sip->srv_query_data);
4860 sip->srv_query_data = NULL;
4862 if (sip->listen_data != NULL)
4863 purple_network_listen_cancel(sip->listen_data);
4864 sip->listen_data = NULL;
4866 if (sip->gsc != NULL)
4867 purple_ssl_close(sip->gsc);
4868 sip->gsc = NULL;
4870 sipe_auth_free(&sip->registrar);
4871 sipe_auth_free(&sip->proxy);
4873 if (sip->txbuf)
4874 purple_circ_buffer_destroy(sip->txbuf);
4875 sip->txbuf = NULL;
4877 g_free(sip->realhostname);
4878 sip->realhostname = NULL;
4880 if (sip->listenpa)
4881 purple_input_remove(sip->listenpa);
4882 sip->listenpa = 0;
4883 if (sip->tx_handler)
4884 purple_input_remove(sip->tx_handler);
4885 sip->tx_handler = 0;
4886 if (sip->resendtimeout)
4887 purple_timeout_remove(sip->resendtimeout);
4888 sip->resendtimeout = 0;
4889 if (sip->timeouts) {
4890 GSList *entry = sip->timeouts;
4891 while (entry) {
4892 struct scheduled_action *sched_action = entry->data;
4893 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
4894 purple_timeout_remove(sched_action->timeout_handler);
4895 g_free(sched_action->payload);
4896 g_free(sched_action->name);
4897 g_free(sched_action);
4898 entry = entry->next;
4901 g_slist_free(sip->timeouts);
4903 if (sip->allow_events) {
4904 GSList *entry = sip->allow_events;
4905 while (entry) {
4906 g_free(entry->data);
4907 entry = entry->next;
4910 g_slist_free(sip->allow_events);
4912 if (sip->contact)
4913 g_free(sip->contact);
4914 sip->contact = NULL;
4915 if (sip->regcallid)
4916 g_free(sip->regcallid);
4917 sip->regcallid = NULL;
4919 sip->fd = -1;
4920 sip->processing_input = FALSE;
4924 * A callback for g_hash_table_foreach_remove
4926 static gboolean sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
4928 sipe_free_buddy(buddy);
4931 static void sipe_close(PurpleConnection *gc)
4933 struct sipe_account_data *sip = gc->proto_data;
4935 if (sip) {
4936 /* leave all conversations */
4937 im_session_close_all(sip);
4939 /* unregister */
4940 do_register_exp(sip, 0);
4942 sipe_connection_cleanup(sip);
4943 g_free(sip->sipdomain);
4944 g_free(sip->username);
4945 g_free(sip->password);
4946 g_free(sip->authdomain);
4947 g_free(sip->authuser);
4948 g_free(sip->status);
4950 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
4951 g_hash_table_destroy(sip->buddies);
4953 g_free(gc->proto_data);
4954 gc->proto_data = NULL;
4957 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
4959 PurpleAccount *acct = purple_connection_get_account(gc);
4960 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
4961 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4962 if (conv == NULL)
4963 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4964 purple_conversation_present(conv);
4965 g_free(id);
4968 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
4971 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4972 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
4975 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
4977 PurpleNotifySearchResults *results;
4978 PurpleNotifySearchColumn *column;
4979 xmlnode *searchResults;
4980 xmlnode *mrow;
4981 int match_count = 0;
4982 gboolean more = FALSE;
4983 gchar *secondary;
4985 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
4987 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
4988 if (!searchResults) {
4989 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4990 return FALSE;
4993 results = purple_notify_searchresults_new();
4995 if (results == NULL) {
4996 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4997 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
4999 xmlnode_free(searchResults);
5000 return FALSE;
5003 column = purple_notify_searchresults_column_new(_("User Name"));
5004 purple_notify_searchresults_column_add(results, column);
5006 column = purple_notify_searchresults_column_new(_("Name"));
5007 purple_notify_searchresults_column_add(results, column);
5009 column = purple_notify_searchresults_column_new(_("Company"));
5010 purple_notify_searchresults_column_add(results, column);
5012 column = purple_notify_searchresults_column_new(_("Country"));
5013 purple_notify_searchresults_column_add(results, column);
5015 column = purple_notify_searchresults_column_new(_("Email"));
5016 purple_notify_searchresults_column_add(results, column);
5018 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5019 GList *row = NULL;
5021 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5022 row = g_list_append(row, g_strdup(uri_parts[1]));
5023 g_strfreev(uri_parts);
5025 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5026 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5027 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5028 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5030 purple_notify_searchresults_row_add(results, row);
5031 match_count++;
5034 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5035 char *data = xmlnode_get_data_unescaped(mrow);
5036 more = (g_strcasecmp(data, "true") == 0);
5037 g_free(data);
5040 secondary = g_strdup_printf(
5041 dngettext(GETTEXT_PACKAGE,
5042 "Found %d contact%s:",
5043 "Found %d contacts%s:", match_count),
5044 match_count, more ? _(" (more matched your query)") : "");
5046 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5047 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5048 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5050 g_free(secondary);
5051 xmlnode_free(searchResults);
5052 return TRUE;
5055 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5057 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5058 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5059 unsigned i = 0;
5061 do {
5062 PurpleRequestField *field = entries->data;
5063 const char *id = purple_request_field_get_id(field);
5064 const char *value = purple_request_field_string_get_value(field);
5066 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5068 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5069 } while ((entries = g_list_next(entries)) != NULL);
5070 attrs[i] = NULL;
5072 if (i > 0) {
5073 gchar *query = g_strjoinv(NULL, attrs);
5074 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5075 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5076 send_soap_request_with_cb(gc->proto_data, body,
5077 (TransCallback) process_search_contact_response, NULL);
5078 g_free(body);
5079 g_free(query);
5082 g_strfreev(attrs);
5085 static void sipe_show_find_contact(PurplePluginAction *action)
5087 PurpleConnection *gc = (PurpleConnection *) action->context;
5088 PurpleRequestFields *fields;
5089 PurpleRequestFieldGroup *group;
5090 PurpleRequestField *field;
5092 fields = purple_request_fields_new();
5093 group = purple_request_field_group_new(NULL);
5094 purple_request_fields_add_group(fields, group);
5096 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5097 purple_request_field_group_add_field(group, field);
5098 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5099 purple_request_field_group_add_field(group, field);
5100 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5101 purple_request_field_group_add_field(group, field);
5102 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5103 purple_request_field_group_add_field(group, field);
5105 purple_request_fields(gc,
5106 _("Search"),
5107 _("Search for a Contact"),
5108 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5109 fields,
5110 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5111 _("_Cancel"), NULL,
5112 purple_connection_get_account(gc), NULL, NULL, gc);
5115 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5117 GList *menu = NULL;
5118 PurplePluginAction *act;
5120 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5121 menu = g_list_prepend(menu, act);
5123 menu = g_list_reverse(menu);
5125 return menu;
5128 static void dummy_permit_deny(PurpleConnection *gc)
5132 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5134 return TRUE;
5138 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5140 return TRUE;
5144 static char *sipe_status_text(PurpleBuddy *buddy)
5146 struct sipe_account_data *sip;
5147 struct sipe_buddy *sbuddy;
5148 char *text = NULL;
5150 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5151 if (sip) //happens on pidgin exit
5153 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5154 if (sbuddy && sbuddy->annotation)
5156 text = g_strdup(sbuddy->annotation);
5160 return text;
5163 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5165 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5166 const PurpleStatus *status = purple_presence_get_active_status(presence);
5167 struct sipe_account_data *sip;
5168 struct sipe_buddy *sbuddy;
5169 char *annotation = NULL;
5171 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5172 if (sip) //happens on pidgin exit
5174 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5175 if (sbuddy)
5177 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5181 //Layout
5182 if (purple_presence_is_online(presence))
5184 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5187 if (annotation)
5189 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5190 g_free(annotation);
5195 static GHashTable *
5196 sipe_get_account_text_table(PurpleAccount *account)
5198 GHashTable *table;
5199 table = g_hash_table_new(g_str_hash, g_str_equal);
5200 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5201 return table;
5204 static PurpleBuddy *
5205 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5207 PurpleBuddy *clone;
5208 const gchar *server_alias, *email;
5209 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5211 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5213 purple_blist_add_buddy(clone, NULL, group, NULL);
5215 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5216 if (server_alias) {
5217 purple_blist_server_alias_buddy(clone, server_alias);
5220 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5221 if (email) {
5222 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5225 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5226 //for UI to update;
5227 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5228 return clone;
5231 static void
5232 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5234 PurpleBuddy *buddy, *b;
5235 PurpleConnection *gc;
5236 PurpleGroup * group = purple_find_group(group_name);
5238 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5240 buddy = (PurpleBuddy *)node;
5242 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5243 gc = purple_account_get_connection(buddy->account);
5245 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5246 if (!b){
5247 b = purple_blist_add_buddy_clone(group, buddy);
5250 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5253 static void
5254 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5256 const gchar *email;
5257 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
5259 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5260 if (email)
5262 char *mailto = g_strdup_printf("mailto:%s", email);
5263 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
5264 #ifndef _WIN32
5266 pid_t pid;
5267 char *const parmList[] = {mailto, NULL};
5268 if ((pid = fork()) == -1)
5270 purple_debug_info("sipe", "fork() error\n");
5272 else if (pid == 0)
5274 execvp("xdg-email", parmList);
5275 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5278 #else
5280 BOOL ret;
5281 _flushall();
5282 errno = 0;
5283 //@TODO resolve env variable %WINDIR% first
5284 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
5285 if (errno)
5287 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
5290 #endif
5292 g_free(mailto);
5294 else
5296 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
5301 * A menu which appear when right-clicking on buddy in contact list.
5303 static GList *
5304 sipe_buddy_menu(PurpleBuddy *buddy)
5306 PurpleBlistNode *g_node;
5307 PurpleGroup *group, *gr_parent;
5308 PurpleMenuAction *act;
5309 GList *menu = NULL;
5310 GList *menu_groups = NULL;
5312 act = purple_menu_action_new(_("Send Email..."),
5313 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5314 NULL, NULL);
5315 menu = g_list_prepend(menu, act);
5317 gr_parent = purple_buddy_get_group(buddy);
5318 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5319 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5320 continue;
5322 group = (PurpleGroup *)g_node;
5323 if (group == gr_parent)
5324 continue;
5326 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5327 continue;
5329 act = purple_menu_action_new(purple_group_get_name(group),
5330 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5331 group->name, NULL);
5332 menu_groups = g_list_prepend(menu_groups, act);
5334 menu_groups = g_list_reverse(menu_groups);
5336 act = purple_menu_action_new(_("Copy to"),
5337 NULL,
5338 NULL, menu_groups);
5339 menu = g_list_prepend(menu, act);
5340 menu = g_list_reverse(menu);
5342 return menu;
5345 GList *sipe_blist_node_menu(PurpleBlistNode *node) {
5346 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5347 return sipe_buddy_menu((PurpleBuddy *) node);
5348 } else {
5349 return NULL;
5353 static gboolean
5354 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
5356 gboolean ret = TRUE;
5357 char *username = (char *)trans->payload;
5359 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
5360 PurpleBuddy *pbuddy;
5361 struct sipe_buddy *sbuddy;
5362 const char *alias;
5363 char *server_alias = NULL;
5364 char *email = NULL;
5365 const char *device_name = NULL;
5367 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
5369 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
5370 alias = purple_buddy_get_local_alias(pbuddy);
5372 if (sip)
5374 //will query buddy UA's capabilities and send answer to log
5375 sipe_options_request(sip, username);
5377 sbuddy = g_hash_table_lookup(sip->buddies, username);
5378 if (sbuddy)
5380 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5384 if (msg->response != 200) {
5385 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
5386 } else {
5387 xmlnode *searchResults;
5388 xmlnode *mrow;
5390 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
5391 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5392 if (!searchResults) {
5393 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5394 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
5395 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
5396 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5397 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
5398 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
5399 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
5400 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
5401 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
5402 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
5403 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
5404 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
5405 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5406 if (!email || strcmp("", email)) {
5407 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
5408 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
5412 xmlnode_free(searchResults);
5415 purple_notify_user_info_add_section_break(info);
5417 if (!server_alias || !strcmp("", server_alias)) {
5418 g_free(server_alias);
5419 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5420 if (server_alias) {
5421 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
5425 // same as server alias, do not present
5426 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
5427 if (alias)
5429 purple_notify_user_info_add_pair(info, _("Alias"), alias);
5432 if (!email || !strcmp("", email)) {
5433 g_free(email);
5434 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
5435 if (email) {
5436 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
5440 if (device_name)
5442 purple_notify_user_info_add_pair(info, _("Device"), device_name);
5445 /* show a buddy's user info in a nice dialog box */
5446 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5447 username, /* buddy's username */
5448 info, /* body */
5449 NULL, /* callback called when dialog closed */
5450 NULL); /* userdata for callback */
5452 return ret;
5456 * AD search first, LDAP based
5458 static void sipe_get_info(PurpleConnection *gc, const char *username)
5460 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5461 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
5463 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
5464 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
5465 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
5466 g_free(body);
5467 g_free(row);
5470 static PurplePlugin *my_protocol = NULL;
5472 static PurplePluginProtocolInfo prpl_info =
5475 NULL, /* user_splits */
5476 NULL, /* protocol_options */
5477 NO_BUDDY_ICONS, /* icon_spec */
5478 sipe_list_icon, /* list_icon */
5479 NULL, /* list_emblems */
5480 sipe_status_text, /* status_text */
5481 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
5482 sipe_status_types, /* away_states */
5483 sipe_blist_node_menu, /* blist_node_menu */
5484 NULL, /* chat_info */
5485 NULL, /* chat_info_defaults */
5486 sipe_login, /* login */
5487 sipe_close, /* close */
5488 sipe_im_send, /* send_im */
5489 NULL, /* set_info */ // TODO maybe
5490 sipe_send_typing, /* send_typing */
5491 sipe_get_info, /* get_info */
5492 sipe_set_status, /* set_status */
5493 NULL, /* set_idle */
5494 NULL, /* change_passwd */
5495 sipe_add_buddy, /* add_buddy */
5496 NULL, /* add_buddies */
5497 sipe_remove_buddy, /* remove_buddy */
5498 NULL, /* remove_buddies */
5499 sipe_add_permit, /* add_permit */
5500 sipe_add_deny, /* add_deny */
5501 sipe_add_deny, /* rem_permit */
5502 sipe_add_permit, /* rem_deny */
5503 dummy_permit_deny, /* set_permit_deny */
5504 NULL, /* join_chat */
5505 NULL, /* reject_chat */
5506 NULL, /* get_chat_name */
5507 NULL, /* chat_invite */
5508 NULL, /* chat_leave */
5509 NULL, /* chat_whisper */
5510 NULL, /* chat_send */
5511 sipe_keep_alive, /* keepalive */
5512 NULL, /* register_user */
5513 NULL, /* get_cb_info */ // deprecated
5514 NULL, /* get_cb_away */ // deprecated
5515 sipe_alias_buddy, /* alias_buddy */
5516 sipe_group_buddy, /* group_buddy */
5517 sipe_rename_group, /* rename_group */
5518 NULL, /* buddy_free */
5519 sipe_convo_closed, /* convo_closed */
5520 purple_normalize_nocase, /* normalize */
5521 NULL, /* set_buddy_icon */
5522 sipe_remove_group, /* remove_group */
5523 NULL, /* get_cb_real_name */ // TODO?
5524 NULL, /* set_chat_topic */
5525 NULL, /* find_blist_chat */
5526 NULL, /* roomlist_get_list */
5527 NULL, /* roomlist_cancel */
5528 NULL, /* roomlist_expand_category */
5529 NULL, /* can_receive_file */
5530 NULL, /* send_file */
5531 NULL, /* new_xfer */
5532 NULL, /* offline_message */
5533 NULL, /* whiteboard_prpl_ops */
5534 sipe_send_raw, /* send_raw */
5535 NULL, /* roomlist_room_serialize */
5536 NULL, /* unregister_user */
5537 NULL, /* send_attention */
5538 NULL, /* get_attention_types */
5540 sizeof(PurplePluginProtocolInfo), /* struct_size */
5541 sipe_get_account_text_table, /* get_account_text_table */
5545 static PurplePluginInfo info = {
5546 PURPLE_PLUGIN_MAGIC,
5547 PURPLE_MAJOR_VERSION,
5548 PURPLE_MINOR_VERSION,
5549 PURPLE_PLUGIN_PROTOCOL, /**< type */
5550 NULL, /**< ui_requirement */
5551 0, /**< flags */
5552 NULL, /**< dependencies */
5553 PURPLE_PRIORITY_DEFAULT, /**< priority */
5554 "prpl-sipe", /**< id */
5555 "Microsoft LCS/OCS", /**< name */
5556 VERSION, /**< version */
5557 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5558 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5559 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5560 "Gabriel Burt <gburt@novell.com>", /**< author */
5561 PURPLE_WEBSITE, /**< homepage */
5562 sipe_plugin_load, /**< load */
5563 sipe_plugin_unload, /**< unload */
5564 sipe_plugin_destroy, /**< destroy */
5565 NULL, /**< ui_info */
5566 &prpl_info, /**< extra_info */
5567 NULL,
5568 sipe_actions,
5569 NULL,
5570 NULL,
5571 NULL,
5572 NULL
5575 static void sipe_plugin_destroy(PurplePlugin *plugin)
5577 GList *entry;
5579 entry = prpl_info.protocol_options;
5580 while (entry) {
5581 purple_account_option_destroy(entry->data);
5582 entry = g_list_delete_link(entry, entry);
5584 prpl_info.protocol_options = NULL;
5586 entry = prpl_info.user_splits;
5587 while (entry) {
5588 purple_account_user_split_destroy(entry->data);
5589 entry = g_list_delete_link(entry, entry);
5591 prpl_info.user_splits = NULL;
5594 static void init_plugin(PurplePlugin *plugin)
5596 PurpleAccountUserSplit *split;
5597 PurpleAccountOption *option;
5599 #ifdef ENABLE_NLS
5600 purple_debug_info(PACKAGE, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
5601 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s",
5602 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
5603 #endif
5605 purple_plugin_register(plugin);
5607 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
5608 purple_account_user_split_set_reverse(split, FALSE);
5609 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
5611 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
5612 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5613 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5614 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5616 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
5617 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5618 // Translators: noun (networking port)
5619 option = purple_account_option_int_new(_("Port"), "port", 5061);
5620 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5622 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
5623 purple_account_option_add_list_item(option, _("Auto"), "auto");
5624 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
5625 purple_account_option_add_list_item(option, _("TCP"), "tcp");
5626 purple_account_option_add_list_item(option, _("UDP"), "udp");
5627 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5629 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5630 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5632 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
5633 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5635 // TODO commented out so won't show in the preferences until we fix krb message signing
5636 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5637 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5639 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5640 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5641 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5644 /*option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5645 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5646 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5647 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5648 my_protocol = plugin;
5651 /* I had to redefined the function for it load, but works */
5652 gboolean purple_init_plugin(PurplePlugin *plugin){
5653 plugin->info = &(info);
5654 init_plugin((plugin));
5655 sipe_plugin_load((plugin));
5656 return purple_plugin_register(plugin);
5660 Local Variables:
5661 mode: c
5662 c-file-style: "bsd"
5663 indent-tabs-mode: t
5664 tab-width: 8
5665 End: